From 5f4cf9235ddd96ccdd55e858ed0e371f60d323df Mon Sep 17 00:00:00 2001 From: Gaukas Wang Date: Thu, 6 Jun 2024 09:54:59 -0600 Subject: [PATCH] docs: update comments and README [ci skip] Signed-off-by: Gaukas Wang --- modcaddy/README.md | 31 +++++++++++++++++++++++++------ modcaddy/handler/handler.go | 23 +++++++++++++++++------ 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/modcaddy/README.md b/modcaddy/README.md index 2236153..060e238 100644 --- a/modcaddy/README.md +++ b/modcaddy/README.md @@ -12,26 +12,26 @@ You will need to use [xcaddy](https://github.com/caddyserver/xcaddy) to rebuild It is worth noting that some web browsers may not choose to switch to QUIC protocol in localhost environment, which may result in the QUIC Client Initial Packet not being sent and therefore not being captured/analyzed. -### Build +## Build ```bash xcaddy build --with github.com/gaukas/clienthellod/modcaddy ``` -#### When build locally with changes +### When build locally with changes ```bash xcaddy build --with github.com/gaukas/clienthellod/modcaddy --with github.com/gaukas/clienthellod/=./ ``` -### Caddyfile +## sample Caddyfile A sample Caddyfile is provided below. ```Caddyfile { # debug # for debugging purpose - # https_port 443 # currently, QUIC listener works only on port 443, otherwise you need to make changes to the code + # https_port 443 # currently, QUIC listener works only on port 443, otherwise you need to make changes to the code order clienthellod before file_server # make sure it hits handler before file_server clienthellod { # app (reservoir) tls_ttl 10s @@ -45,7 +45,6 @@ A sample Caddyfile is provided below. } tls } - # protocols h3 } } @@ -70,4 +69,24 @@ A sample Caddyfile is provided below. root /var/www/html } } -``` \ No newline at end of file +``` + +## Known issues + +### QUIC can't be fingerprinted when web browser chooses H2 not H3 + +Under certain network condition or configurations, a web browser may decide not to switch to QUIC protocol even when the server advertises the support for QUIC. This issue is more likely to happen under the scenarios with low-latency such as in localhost/intranet. + +There is no trivial solution to this issue, as there seems to be no way to force the web browser to use QUIC. + +### QUIC fingerprint missing for the first request + +It is possible that a client sends both H2-over-TCP (TLS) and H3-over-UDP (QUIC) for the first time requesting a web page and decide to render the response from H2-over-TCP (TLS). In this case, the QUIC Client Initial Packet might be not yet recorded. + +Reloading the page might help by fetching the cached QUIC fingerprint if it is captured and not yet expired. + +### Fingerprint gone after reloading/refreshing the web page + +Some web browsers may decide to reuse the existing unclosed connection for new HTTP requests instead of establishing a new one by sending a new TLS Client Hello or QUIC Initial Packet(s). In which case, no new fingerprint will be captured and if the old fingerprint is expired or otherwise removed, the fingerprint will be gone and nothing will be displayed. + +Forcing the web browser to establish a new connection by closing the existing connection, opening a new tab, or use different domain names every time might help. \ No newline at end of file diff --git a/modcaddy/handler/handler.go b/modcaddy/handler/handler.go index 5990bf3..86cd622 100644 --- a/modcaddy/handler/handler.go +++ b/modcaddy/handler/handler.go @@ -119,11 +119,17 @@ func (h *Handler) serveTLS(wr http.ResponseWriter, req *http.Request, next caddy return next.ServeHTTP(wr, req) } - // write JSON to response + // Properly set the Content-Type header wr.Header().Set("Content-Type", "application/json") + + // Close the HTTP connection after sending the response + // + // HTTP/1.X only. Forbidden in HTTP/2 (RFC 9113 Section 8.2.2) + // and HTTP/3 (RFC 9114 Section 4.2) if req.ProtoMajor == 1 { - wr.Header().Set("Connection", "close") // HTTP/1 only. Forbidden in HTTP/2, HTTP/3 + wr.Header().Set("Connection", "close") } + _, err = wr.Write(b) if err != nil { h.logger.Error("failed to write response", zap.Error(err)) @@ -136,7 +142,6 @@ func (h *Handler) serveTLS(wr http.ResponseWriter, req *http.Request, next caddy // reservoir and writing it to the response. func (h *Handler) serveQUIC(wr http.ResponseWriter, req *http.Request, next caddyhttp.Handler) error { // skipcq: GO-W1029 var from string - if req.ProtoMajor == 3 { from = req.RemoteAddr } else { @@ -159,7 +164,7 @@ func (h *Handler) serveQUIC(wr http.ResponseWriter, req *http.Request, next cadd // get the client hello from the reservoir qfp, err := h.reservoir.QUICFingerprinter().PeekAwait(from) if err != nil { - h.logger.Error(fmt.Sprintf("Unable to fetch QUIC fingerprint sent by %s: %v", req.RemoteAddr, err)) + h.logger.Debug(fmt.Sprintf("Unable to fetch QUIC fingerprint sent by %s: %v", req.RemoteAddr, err)) return next.ServeHTTP(wr, req) } @@ -187,11 +192,17 @@ func (h *Handler) serveQUIC(wr http.ResponseWriter, req *http.Request, next cadd return next.ServeHTTP(wr, req) } - // write JSON to response + // Properly set the Content-Type header wr.Header().Set("Content-Type", "application/json") + + // Close the HTTP connection after sending the response + // + // HTTP/1.X only. Forbidden in HTTP/2 (RFC 9113 Section 8.2.2) + // and HTTP/3 (RFC 9114 Section 4.2) if req.ProtoMajor == 1 { - wr.Header().Set("Connection", "close") // HTTP/1 only. Forbidden in HTTP/2, HTTP/3 + wr.Header().Set("Connection", "close") } + _, err = wr.Write(b) if err != nil { h.logger.Error("failed to write response", zap.Error(err))