@@ -263,9 +263,9 @@ enum socks5_state_e {
263263
264264static void socks5_handshake (hio_t * io );
265265
266- static void socks5_fail (hio_t * io ) {
266+ static void proxy_fail (hio_t * io ) {
267267 if (io -> error == 0 ) io -> error = ERR_CONNECT ;
268- hlogw ("connfd=%d socks5 handshake error" , io -> fd );
268+ hlogw ("connfd=%d proxy handshake error" , io -> fd );
269269 hio_close (io );
270270}
271271
@@ -277,16 +277,18 @@ static void socks5_expect(hio_t* io, int state, int want) {
277277 s5 -> want = want ;
278278}
279279
280- // Raw handshake send. The SOCKS5 handshake runs immediately after the TCP
280+ // Raw handshake send. The proxy handshake runs immediately after the TCP
281281// connection to the proxy is established, when the socket send buffer is empty
282- // and the messages are tiny (<= 513 bytes), so a short write is not expected.
282+ // and the message is tiny (SOCKS5 <= 513 bytes; HTTP CONNECT < ~1.3KB), far
283+ // smaller than the default send buffer, so a single send() transfers it all.
283284// We deliberately do NOT use hio_write() here: it would invoke the upper-layer
284285// write_cb (leaking handshake bytes, including credentials, to the application
285286// before onConnection), dispatch to hssl_write() with a not-yet-created SSL
286287// handle for a TLS target, and enqueue on EAGAIN via hio_add() which would
287- // clobber the handshake read handler. A short write or error is treated as
288- // fatal and closes the connection.
289- static int socks5_send (hio_t * io , const void * buf , int len ) {
288+ // clobber the handshake read handler (io has a single cb slot). A short write
289+ // cannot happen here in practice; if it somehow does, it is treated as fatal
290+ // (return -1) rather than blocking the event loop.
291+ static int proxy_send (hio_t * io , const void * buf , int len ) {
290292 int flag = 0 ;
291293#ifdef MSG_NOSIGNAL
292294 flag |= MSG_NOSIGNAL ;
@@ -300,15 +302,15 @@ static void socks5_send_connect(hio_t* io) {
300302 proxy_conn_t * s5 = io -> proxy ;
301303 unsigned char buf [300 ];
302304 int n = socks5_build_connect_request (s5 , buf );
303- if (n < 0 ) { socks5_fail (io ); return ; }
304- if (socks5_send (io , buf , n ) != 0 ) { socks5_fail (io ); return ; }
305+ if (n < 0 ) { proxy_fail (io ); return ; }
306+ if (proxy_send (io , buf , n ) != 0 ) { proxy_fail (io ); return ; }
305307 socks5_expect (io , S5_RECV_REPLY_HEAD , 4 );
306308}
307309
308310// hand off the established proxy tunnel to the upper layer: stop the handshake
309311// read handler, then run the SSL handshake / connect_cb. io->read_cb was never
310312// touched, so the upper-layer Channel read callback stays intact.
311- static void socks5_established (hio_t * io ) {
313+ static void proxy_established (hio_t * io ) {
312314 hio_del (io , HV_READ );
313315 nio_connect_established (io );
314316}
@@ -321,29 +323,29 @@ static void socks5_dispatch(hio_t* io) {
321323 switch (s5 -> state ) {
322324 case S5_RECV_METHOD :
323325 // VER METHOD
324- if (buf [0 ] != SOCKS5_VERSION ) { socks5_fail (io ); return ; }
326+ if (buf [0 ] != SOCKS5_VERSION ) { proxy_fail (io ); return ; }
325327 if (buf [1 ] == SOCKS5_AUTH_NONE ) {
326328 socks5_send_connect (io );
327329 } else if (buf [1 ] == SOCKS5_AUTH_USERPASS && s5 -> setting .username [0 ]) {
328330 unsigned char req [640 ];
329331 int n = socks5_build_auth_request (s5 , req );
330- if (socks5_send (io , req , n ) != 0 ) { socks5_fail (io ); return ; }
332+ if (proxy_send (io , req , n ) != 0 ) { proxy_fail (io ); return ; }
331333 socks5_expect (io , S5_RECV_AUTH , 2 );
332334 } else {
333- socks5_fail (io ); // no acceptable method
335+ proxy_fail (io ); // no acceptable method
334336 }
335337 return ;
336338
337339 case S5_RECV_AUTH :
338340 // VER STATUS (0 == success)
339- if (buf [1 ] != 0x00 ) { socks5_fail (io ); return ; }
341+ if (buf [1 ] != 0x00 ) { proxy_fail (io ); return ; }
340342 socks5_send_connect (io );
341343 return ;
342344
343345 case S5_RECV_REPLY_HEAD : {
344346 // VER REP RSV ATYP
345- if (buf [0 ] != SOCKS5_VERSION ) { socks5_fail (io ); return ; }
346- if (buf [1 ] != SOCKS5_REP_SUCCESS ) { io -> error = ERR_CONNECT ; socks5_fail (io ); return ; }
347+ if (buf [0 ] != SOCKS5_VERSION ) { proxy_fail (io ); return ; }
348+ if (buf [1 ] != SOCKS5_REP_SUCCESS ) { io -> error = ERR_CONNECT ; proxy_fail (io ); return ; }
347349 unsigned char atyp = buf [3 ];
348350 if (atyp == SOCKS5_ATYP_IPV4 ) {
349351 socks5_expect (io , S5_RECV_REPLY_ADDR , 4 + 2 ); // addr + port
@@ -354,14 +356,14 @@ static void socks5_dispatch(hio_t* io) {
354356 // first requiring the length byte.
355357 socks5_expect (io , S5_RECV_REPLY_DADDR , 1 );
356358 } else {
357- socks5_fail (io );
359+ proxy_fail (io );
358360 }
359361 return ;
360362 }
361363
362364 case S5_RECV_REPLY_ADDR :
363365 // bound addr+port consumed; tunnel is up
364- socks5_established (io );
366+ proxy_established (io );
365367 return ;
366368
367369 case S5_RECV_REPLY_DADDR :
@@ -372,11 +374,11 @@ static void socks5_dispatch(hio_t* io) {
372374 socks5_expect (io , S5_RECV_REPLY_DADDR , dlen + 2 );
373375 return ;
374376 }
375- socks5_established (io );
377+ proxy_established (io );
376378 return ;
377379
378380 default :
379- socks5_fail (io );
381+ proxy_fail (io );
380382 return ;
381383 }
382384}
@@ -387,14 +389,14 @@ static void socks5_handshake(hio_t* io) {
387389 proxy_conn_t * s5 = io -> proxy ;
388390 while (s5 -> rlen < s5 -> want ) {
389391 int need = s5 -> want - s5 -> rlen ;
390- if (s5 -> want > (int )sizeof (s5 -> rbuf )) { socks5_fail (io ); return ; }
392+ if (s5 -> want > (int )sizeof (s5 -> rbuf )) { proxy_fail (io ); return ; }
391393 int n = recv (io -> fd , (char * )s5 -> rbuf + s5 -> rlen , need , 0 );
392- if (n == 0 ) { socks5_fail (io ); return ; } // peer closed
394+ if (n == 0 ) { proxy_fail (io ); return ; } // peer closed
393395 if (n < 0 ) {
394396 int err = socket_errno ();
395397 if (err == EAGAIN || err == EINTR ) return ; // wait for more
396398 io -> error = err ;
397- socks5_fail (io );
399+ proxy_fail (io );
398400 return ;
399401 }
400402 s5 -> rlen += n ;
@@ -407,17 +409,100 @@ static void socks5_handshake_start(hio_t* io) {
407409 proxy_conn_t * s5 = io -> proxy ;
408410 unsigned char buf [8 ];
409411 int n = socks5_build_method_request (s5 , buf );
410- if (socks5_send (io , buf , n ) != 0 ) { socks5_fail (io ); return ; }
412+ if (proxy_send (io , buf , n ) != 0 ) { proxy_fail (io ); return ; }
411413 socks5_expect (io , S5_RECV_METHOD , 2 );
412414 hio_add (io , socks5_handshake , HV_READ );
413415}
414416
415- // Dispatch the proxy handshake by protocol (only SOCKS5 implemented so far).
417+ // HTTP CONNECT handshake (RFC 7231 4.3.6): send a CONNECT request, then read
418+ // response headers until the blank line "\r\n\r\n". A 2xx status establishes
419+ // the tunnel. Like the SOCKS5 handshake this uses a dedicated recv() via
420+ // hio_add (never touches io->read_cb) and is robust to fragmentation.
421+ //
422+ // CONNECT responses carry no body, but a server-first origin protocol (SMTP,
423+ // IMAP, FTP...) may send its greeting immediately after the tunnel opens, so
424+ // those bytes can arrive in the same segment as the response headers. To avoid
425+ // swallowing them, we MSG_PEEK to locate the header terminator, then drain
426+ // EXACTLY the header bytes with a real recv(); anything after "\r\n\r\n" stays
427+ // in the socket for the upper-layer read path.
428+ static void http_connect_handshake (hio_t * io ) {
429+ proxy_conn_t * p = io -> proxy ;
430+ for (;;) {
431+ int cap = (int )sizeof (p -> rbuf ) - p -> rlen ;
432+ if (cap <= 0 ) { proxy_fail (io ); return ; } // headers too large
433+ // peek (non-destructive): inspect what is available without consuming.
434+ int n = recv (io -> fd , (char * )p -> rbuf + p -> rlen , cap , MSG_PEEK );
435+ if (n == 0 ) { proxy_fail (io ); return ; } // peer closed
436+ if (n < 0 ) {
437+ int err = socket_errno ();
438+ if (err == EAGAIN || err == EINTR ) return ; // wait for more
439+ io -> error = err ;
440+ proxy_fail (io );
441+ return ;
442+ }
443+ int have = p -> rlen + n ;
444+ // search for "\r\n\r\n" in the peeked window (rescan from a safe offset)
445+ int start = p -> rlen >= 3 ? p -> rlen - 3 : 0 ;
446+ int term = -1 ;
447+ for (int i = start + 3 ; i < have ; ++ i ) {
448+ if (p -> rbuf [i - 3 ]== '\r' && p -> rbuf [i - 2 ]== '\n' &&
449+ p -> rbuf [i - 1 ]== '\r' && p -> rbuf [i ]== '\n' ) { term = i ; break ; }
450+ }
451+ if (term < 0 ) {
452+ // no full header yet: consume the peeked bytes into the accumulator
453+ // (they are all header bytes) and keep reading.
454+ int got = recv (io -> fd , (char * )p -> rbuf + p -> rlen , n , 0 );
455+ if (got <= 0 ) { proxy_fail (io ); return ; }
456+ p -> rlen += got ;
457+ continue ;
458+ }
459+ // full header present. Drain exactly up to and including the terminator,
460+ // leaving any trailing tunnel/greeting bytes in the socket.
461+ int header_len = term + 1 ; // bytes from socket start
462+ int to_drain = header_len - p -> rlen ; // not yet consumed
463+ if (to_drain > 0 ) {
464+ int got = recv (io -> fd , (char * )p -> rbuf + p -> rlen , to_drain , 0 );
465+ if (got != to_drain ) { proxy_fail (io ); return ; }
466+ p -> rlen += got ;
467+ }
468+ // parse status line: "HTTP/1.x SP CODE SP ..."
469+ int code = 0 ;
470+ char * sp = (char * )memchr (p -> rbuf , ' ' , p -> rlen );
471+ if (sp ) code = atoi (sp + 1 );
472+ if (code >= 200 && code < 300 ) {
473+ proxy_established (io );
474+ } else {
475+ hlogw ("connfd=%d http proxy CONNECT failed: %d" , io -> fd , code );
476+ io -> error = ERR_CONNECT ;
477+ proxy_fail (io );
478+ }
479+ return ;
480+ }
481+ }
482+
483+ // Kick off the HTTP CONNECT handshake once the TCP connection to the proxy is up.
484+ static void http_connect_start (hio_t * io ) {
485+ proxy_conn_t * p = io -> proxy ;
486+ // Max request: "CONNECT " + authority(<=262) + " HTTP/1.1\r\nHost: " +
487+ // authority + "\r\nProxy-Authorization: Basic " + base64(255:255)=~684 +
488+ // "\r\n\r\n" ~= 1.3KB. 2048 leaves headroom.
489+ char buf [2048 ];
490+ int n = http_connect_build_request (p , buf , (int )sizeof (buf ));
491+ if (n < 0 ) { proxy_fail (io ); return ; }
492+ if (proxy_send (io , buf , n ) != 0 ) { proxy_fail (io ); return ; }
493+ p -> rlen = 0 ;
494+ hio_add (io , http_connect_handshake , HV_READ );
495+ }
496+
497+ // Dispatch the proxy handshake by protocol.
416498static void proxy_handshake_start (hio_t * io ) {
417499 switch (io -> proxy -> setting .protocol ) {
418500 case PROXY_PROTOCOL_SOCKS5 :
419501 socks5_handshake_start (io );
420502 return ;
503+ case PROXY_PROTOCOL_HTTP_CONNECT :
504+ http_connect_start (io );
505+ return ;
421506 default :
422507 io -> error = ERR_INVALID_PARAM ;
423508 hio_close (io );
0 commit comments