proxyOptimistic peeks at buffered bytes available on the client side to guess the protocol. If a known protocol is found, it passes control to that protocol's handler. Otherwise, it falls back to the raw handler.
(cli, srv *bufConn)
| 205 | // guess the protocol. If a known protocol is found, it passes control to that |
| 206 | // protocol's handler. Otherwise, it falls back to the raw handler. |
| 207 | func (p *proxy) proxyOptimistic(cli, srv *bufConn) error { |
| 208 | slog.Debug("starting proxyOptimistic", "proxy", p) |
| 209 | |
| 210 | errs := make(chan error, 2) |
| 211 | |
| 212 | // While we need to sample client's TCP bytes to see if it's HTTP and/or TLS |
| 213 | // or something else, we can't synchronously wait for the client's first |
| 214 | // bytes. Doing so would break protocols that expect the server side to |
| 215 | // talk first. HTTP and TLS don't behave this way, but since those are not |
| 216 | // the only protocols we'll be proxying (we intercept all TCP connections at |
| 217 | // the socket level), the sampling strategy needs to be robust. |
| 218 | var race atomic.Bool |
| 219 | |
| 220 | go func() { |
| 221 | if _, err := srv.peekSample(); err != nil { |
| 222 | if race.Load() { |
| 223 | errs <- fmt.Errorf("server: peek sample: %w", err) |
| 224 | } else { |
| 225 | errs <- nil |
| 226 | } |
| 227 | return |
| 228 | } |
| 229 | |
| 230 | if isHTTP2Enabled { |
| 231 | // Give the client up to few hundred milliseconds to catch up so it has a |
| 232 | // better chance of winning the race in situations where the first server |
| 233 | // bytes arrive fractions of a millisecond before the first client bytes |
| 234 | // (e.g. when a HTTP/2 server pushes a SETTINGS frame before the client |
| 235 | // sends its first frame). Doing this greatly increases wire protocol |
| 236 | // guess accuracy. While this admittedly adds upto 1ms latency in obscure |
| 237 | // protocols where the server is required to talk first, the trade-off is |
| 238 | // well worth it because Subtrace is not designed for those protocols. |
| 239 | start := time.Now() |
| 240 | for i := 0; ; i++ { |
| 241 | if race.Load() { |
| 242 | break |
| 243 | } |
| 244 | if i&15 == 15 { |
| 245 | time.Sleep(20 * time.Microsecond) |
| 246 | } |
| 247 | if race.Load() { |
| 248 | break |
| 249 | } |
| 250 | if time.Since(start) >= 1000*time.Microsecond { |
| 251 | break |
| 252 | } |
| 253 | if race.Load() { |
| 254 | break |
| 255 | } |
| 256 | runtime.Gosched() |
| 257 | if race.Load() { |
| 258 | break |
| 259 | } |
| 260 | } |
| 261 | } |
| 262 | |
| 263 | if winner := race.CompareAndSwap(false, true); !winner { |
| 264 | errs <- nil |
no test coverage detected