openSession dials the upstream target, creates a Session for the given ID, registers it under the given owner, and spawns the bidirectional pump goroutines.
(id [frame.SessionIDLen]byte, target string, owner [frame.ClientIDLen]byte)
| 567 | // registers it under the given owner, and spawns the bidirectional pump |
| 568 | // goroutines. |
| 569 | func (s *Server) openSession(id [frame.SessionIDLen]byte, target string, owner [frame.ClientIDLen]byte) (*session.Session, error) { |
| 570 | var upstream net.Conn |
| 571 | var res *dialResult |
| 572 | if s.cfg.UpstreamProxy != "" { |
| 573 | // Let the SOCKS5 proxy handle DNS so the target hostname is resolved |
| 574 | // on the proxy side (e.g. through WARP), not locally on the VPS. |
| 575 | conn, err := s.dial("tcp", target, 15*time.Second) |
| 576 | if err != nil { |
| 577 | return nil, err |
| 578 | } |
| 579 | upstream = conn |
| 580 | } else { |
| 581 | var err error |
| 582 | res, err = dialWithDNSCache(s.dns, s.dial, "tcp", target, 15*time.Second) |
| 583 | if err != nil { |
| 584 | return nil, err |
| 585 | } |
| 586 | upstream = res.Conn |
| 587 | } |
| 588 | // Disable Nagle's algorithm so small writes (TLS handshake records, HTTP |
| 589 | // request lines) hit the wire immediately instead of waiting up to 40 ms |
| 590 | // to coalesce. Interactive workloads dominate this tunnel; throughput-bound |
| 591 | // flows already buffer at the kernel level. |
| 592 | if tcpConn, ok := upstream.(*net.TCPConn); ok { |
| 593 | _ = tcpConn.SetNoDelay(true) |
| 594 | } |
| 595 | if s.debugTiming { |
| 596 | if res != nil { |
| 597 | log.Printf("[timing] %x dial dns=%dms cached=%v tcp=%dms target=%s", |
| 598 | id[:4], res.DNS.Milliseconds(), res.DNSCached, res.TCP.Milliseconds(), target) |
| 599 | } else { |
| 600 | log.Printf("[timing] %x dial via proxy target=%s", id[:4], target) |
| 601 | } |
| 602 | } |
| 603 | dialedAt := time.Now() |
| 604 | sess := session.New(id, target, false) |
| 605 | sess.OnTx = func() { |
| 606 | s.mu.Lock() |
| 607 | s.txReady[id] = struct{}{} |
| 608 | s.mu.Unlock() |
| 609 | s.kick(owner) |
| 610 | } |
| 611 | |
| 612 | s.mu.Lock() |
| 613 | s.sessions[id] = sess |
| 614 | s.sessionOwners[id] = owner |
| 615 | s.upstreams[id] = upstream |
| 616 | s.firstReply[id] = struct{}{} |
| 617 | s.lastActivity[id] = time.Now() |
| 618 | s.mu.Unlock() |
| 619 | s.stats.sessionsOpen.Add(1) |
| 620 | |
| 621 | log.Printf("[exit] new session %x owner=%x -> %s", id[:4], owner[:4], target) |
| 622 | |
| 623 | // Upstream → session.EnqueueTx (downstream direction). |
| 624 | go func() { |
| 625 | defer upstream.Close() |
| 626 | bufP := s.upstreamReadPool.Get().(*[]byte) |