This commit is contained in:
2026-08-16 13:17:19 -03:00
parent c0a337be3f
commit 7b8e7bfbd0
82 changed files with 5479 additions and 1016 deletions
+507
View File
@@ -0,0 +1,507 @@
package main
import (
"bytes"
"context"
"encoding/binary"
"fmt"
"net"
"sync"
"time"
"dragontcp/internal/wire"
)
type streamSession struct {
sid wire.SessionID
target net.Conn
targetName string
maxChunk int
maxBuffer int
debug *serverDebug
mu sync.Mutex
notify chan struct{}
buf []byte
base uint64
eof bool
closed bool
lastSeen time.Time
upMu sync.Mutex
expectedUp uint64
}
func newStreamSession(sid wire.SessionID, target net.Conn, targetName string, maxChunk, maxBuffer int, debug *serverDebug) *streamSession {
s := &streamSession{
sid: sid,
target: target,
targetName: targetName,
maxChunk: maxChunk,
maxBuffer: maxBuffer,
debug: debug,
notify: make(chan struct{}),
lastSeen: time.Now(),
}
go s.readTarget()
return s
}
func (s *streamSession) signalLocked() {
close(s.notify)
s.notify = make(chan struct{})
}
func (s *streamSession) touchLocked() { s.lastSeen = time.Now() }
func (s *streamSession) readTarget() {
tmp := make([]byte, 64*1024)
for {
n, err := s.target.Read(tmp)
if n > 0 {
data := append([]byte(nil), tmp[:n]...)
for len(data) > 0 {
s.mu.Lock()
for !s.closed && len(s.buf) >= s.maxBuffer {
ch := s.notify
s.mu.Unlock()
<-ch
s.mu.Lock()
}
if s.closed {
s.mu.Unlock()
return
}
room := s.maxBuffer - len(s.buf)
take := len(data)
if take > room {
take = room
}
s.buf = append(s.buf, data[:take]...)
data = data[take:]
s.touchLocked()
s.signalLocked()
s.mu.Unlock()
if s.debug != nil && s.debug.enabled {
s.debug.bytesDown.Add(uint64(take))
}
}
}
if err != nil {
s.mu.Lock()
if !s.closed {
s.eof = true
s.touchLocked()
s.signalLocked()
}
s.mu.Unlock()
return
}
}
}
func (s *streamSession) ackLocked(offset uint64) {
if offset <= s.base {
return
}
end := s.base + uint64(len(s.buf))
if offset > end {
offset = end
}
drop := int(offset - s.base)
if drop <= 0 {
return
}
s.buf = s.buf[drop:]
s.base = offset
if len(s.buf) == 0 {
s.buf = nil
} else if cap(s.buf) > 4*len(s.buf) && cap(s.buf) > 1024*1024 {
compact := append([]byte(nil), s.buf...)
s.buf = compact
}
s.signalLocked()
}
func (s *streamSession) ack(offset uint64) {
s.mu.Lock()
s.ackLocked(offset)
s.touchLocked()
s.mu.Unlock()
}
func (s *streamSession) readAt(offset uint64, limit int, wait time.Duration) ([]byte, byte, error) {
if limit < 1 || limit > s.maxChunk {
return nil, wire.StatusError, fmt.Errorf("invalid download limit %d", limit)
}
deadline := time.Now().Add(wait)
firstDataAt := time.Time{}
for {
s.mu.Lock()
s.touchLocked()
if offset < s.base {
s.mu.Unlock()
return nil, wire.StatusError, fmt.Errorf("download offset %d was already acknowledged (base=%d)", offset, s.base)
}
rel64 := offset - s.base
if rel64 <= uint64(len(s.buf)) {
rel := int(rel64)
available := len(s.buf) - rel
if available > 0 {
if firstDataAt.IsZero() {
firstDataAt = time.Now()
}
// Coalesce tiny target reads briefly. This prevents a 1-2 byte
// producer read from becoming a permanent tiny tunnel record.
if available < limit && !s.eof && wait > 0 && time.Since(firstDataAt) < 2*time.Millisecond {
ch := s.notify
s.mu.Unlock()
select {
case <-ch:
case <-time.After(2 * time.Millisecond):
}
continue
}
n := available
if n > limit {
n = limit
}
out := append([]byte(nil), s.buf[rel:rel+n]...)
s.mu.Unlock()
return out, wire.StatusData, nil
}
if s.eof || s.closed {
s.mu.Unlock()
return nil, wire.StatusEOF, nil
}
} else {
s.mu.Unlock()
return nil, wire.StatusError, fmt.Errorf("download offset %d is beyond buffered stream end %d", offset, s.base+uint64(len(s.buf)))
}
if wait <= 0 || time.Now().After(deadline) {
s.mu.Unlock()
return nil, wire.StatusWait, nil
}
ch := s.notify
remaining := time.Until(deadline)
s.mu.Unlock()
select {
case <-ch:
case <-time.After(remaining):
return nil, wire.StatusWait, nil
}
}
}
func (s *streamSession) upload(offset uint64, data []byte) error {
if len(data) == 0 || len(data) > s.maxChunk {
return fmt.Errorf("invalid upload size %d", len(data))
}
s.upMu.Lock()
defer s.upMu.Unlock()
if offset < s.expectedUp {
// Idempotent retry after a lost ACK.
if offset+uint64(len(data)) <= s.expectedUp {
return nil
}
return fmt.Errorf("overlapping upload retry at %d", offset)
}
if offset != s.expectedUp {
return fmt.Errorf("upload gap: got %d expected %d", offset, s.expectedUp)
}
if _, err := s.target.Write(data); err != nil {
return err
}
s.expectedUp += uint64(len(data))
s.mu.Lock()
s.touchLocked()
s.mu.Unlock()
if s.debug != nil && s.debug.enabled {
s.debug.bytesUp.Add(uint64(len(data)))
s.debug.pushRecords.Add(1)
}
return nil
}
func (s *streamSession) close() {
s.mu.Lock()
if s.closed {
s.mu.Unlock()
return
}
s.closed = true
s.signalLocked()
s.mu.Unlock()
_ = s.target.Close()
}
type streamManager struct {
mu sync.RWMutex
sessions map[string]*streamSession
timeout time.Duration
debug *serverDebug
}
func sidKey(sid wire.SessionID) string { return string(sid[:]) }
func newStreamManager(timeout time.Duration, debug *serverDebug) *streamManager {
m := &streamManager{sessions: make(map[string]*streamSession), timeout: timeout, debug: debug}
go m.cleanupLoop()
return m
}
func (m *streamManager) get(sid wire.SessionID) *streamSession {
m.mu.RLock()
s := m.sessions[sidKey(sid)]
m.mu.RUnlock()
return s
}
func (m *streamManager) addOrGet(sid wire.SessionID, s *streamSession) (*streamSession, bool) {
key := sidKey(sid)
m.mu.Lock()
if old := m.sessions[key]; old != nil {
m.mu.Unlock()
s.close()
return old, false
}
m.sessions[key] = s
m.mu.Unlock()
return s, true
}
func (m *streamManager) remove(sid wire.SessionID) {
key := sidKey(sid)
m.mu.Lock()
s := m.sessions[key]
delete(m.sessions, key)
m.mu.Unlock()
if s != nil {
s.close()
if m.debug != nil && m.debug.enabled {
m.debug.sessionsClosed.Add(1)
m.debug.activeSessions.Add(-1)
}
}
}
func (m *streamManager) count() int { m.mu.RLock(); n := len(m.sessions); m.mu.RUnlock(); return n }
func (m *streamManager) cleanupLoop() {
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
for range ticker.C {
cutoff := time.Now().Add(-m.timeout)
var stale []wire.SessionID
m.mu.RLock()
for _, s := range m.sessions {
s.mu.Lock()
last := s.lastSeen
closed := s.closed
sid := s.sid
s.mu.Unlock()
if closed || last.Before(cutoff) {
stale = append(stale, sid)
}
}
m.mu.RUnlock()
for _, sid := range stale {
m.remove(sid)
}
}
}
func parseProbe(payload []byte) (kind byte, value int, token string, err error) {
if len(payload) < 11 || !bytes.Equal(payload[:4], wire.ProbeMagic[:]) {
return 0, 0, "", fmt.Errorf("bad probe payload")
}
kind = payload[4]
tl := int(binary.BigEndian.Uint16(payload[5:7]))
value = int(binary.BigEndian.Uint32(payload[7:11]))
if 11+tl > len(payload) {
return 0, 0, "", fmt.Errorf("bad probe token length")
}
token = string(payload[11 : 11+tl])
return
}
func probePattern(n int) []byte {
out := make([]byte, n)
for i := range out {
out[i] = byte((i*31 + 17) & 0xff)
}
return out
}
func parseOpen(payload []byte) (token, host string, port int, err error) {
if len(payload) < 6 {
return "", "", 0, fmt.Errorf("bad OPEN payload")
}
tl := int(binary.BigEndian.Uint16(payload[0:2]))
hl := int(binary.BigEndian.Uint16(payload[2:4]))
port = int(binary.BigEndian.Uint16(payload[4:6]))
if port < 1 || 6+tl+hl != len(payload) {
return "", "", 0, fmt.Errorf("bad OPEN lengths")
}
token = string(payload[6 : 6+tl])
host = string(payload[6+tl:])
if host == "" {
return "", "", 0, fmt.Errorf("empty target host")
}
return
}
func processWireRequest(conn net.Conn, req wire.Request, token string, allowPrivate bool, cache *dnsCache, tcpBuffer int, manager *streamManager, maxChunk, maxBuffer int, pollWait time.Duration, debug *serverDebug) error {
switch req.Mode {
case wire.ModeProbe:
kind, value, supplied, err := parseProbe(req.Payload)
if err != nil {
return wire.WriteResponse(conn, wire.StatusError, []byte(err.Error()))
}
if !tokenEqual(supplied, token) {
return wire.WriteResponse(conn, wire.StatusError, []byte("authentication failed"))
}
switch kind {
case wire.ProbeUpload:
if len(req.Payload) > maxChunk {
return wire.WriteResponse(conn, wire.StatusError, []byte("probe too large"))
}
return wire.WriteResponse(conn, wire.StatusOK, nil)
case wire.ProbeDownload:
if value < 1 || value > maxChunk {
return wire.WriteResponse(conn, wire.StatusError, []byte("probe too large"))
}
return wire.WriteMaskedResponse(conn, wire.StatusData, probePattern(value), req.Session, wire.ModeProbe, req.Seq)
case wire.ProbeKeepalive:
return wire.WriteResponse(conn, wire.StatusOK, nil)
case wire.ProbeBatch:
count := value
if count < 1 {
count = 1
}
if count > 16 {
count = 16
}
for i := 0; i < count; i++ {
data := probePattern(32)
if err := wire.WriteMaskedResponse(conn, wire.StatusData, data, req.Session, wire.ModeProbe, req.Seq+uint64(i)); err != nil {
return err
}
}
return nil
default:
return wire.WriteResponse(conn, wire.StatusError, []byte("unknown probe kind"))
}
case wire.ModeOpen:
supplied, host, port, err := parseOpen(req.Payload)
if err != nil {
return wire.WriteResponse(conn, wire.StatusError, []byte(err.Error()))
}
if !tokenEqual(supplied, token) {
return wire.WriteResponse(conn, wire.StatusError, []byte("authentication failed"))
}
if old := manager.get(req.Session); old != nil {
body := make([]byte, 4)
binary.BigEndian.PutUint32(body, uint32(maxChunk))
return wire.WriteMaskedResponse(conn, wire.StatusOK, body, req.Session, wire.ModeOpen, req.Seq)
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
target, err := dialTarget(ctx, host, port, allowPrivate, cache, tcpBuffer)
cancel()
if err != nil {
return wire.WriteResponse(conn, wire.StatusError, []byte(err.Error()))
}
session := newStreamSession(req.Session, target, fmt.Sprintf("%s:%d", host, port), maxChunk, maxBuffer, debug)
_, created := manager.addOrGet(req.Session, session)
if created && debug != nil && debug.enabled {
debug.sessionsOpened.Add(1)
debug.activeSessions.Add(1)
debug.logf("SESSION OPEN sid=%x target=%s:%d active_sessions=%d", req.Session[:4], host, port, manager.count())
}
body := make([]byte, 4)
binary.BigEndian.PutUint32(body, uint32(maxChunk))
return wire.WriteMaskedResponse(conn, wire.StatusOK, body, req.Session, wire.ModeOpen, req.Seq)
case wire.ModeUpload:
s := manager.get(req.Session)
if s == nil {
return wire.WriteResponse(conn, wire.StatusError, []byte("unknown session"))
}
if len(req.Payload) > maxChunk {
return wire.WriteResponse(conn, wire.StatusError, []byte("upload too large"))
}
if err := s.upload(req.Seq, req.Payload); err != nil {
return wire.WriteResponse(conn, wire.StatusError, []byte(err.Error()))
}
return wire.WriteResponse(conn, wire.StatusOK, nil)
case wire.ModeDownload:
s := manager.get(req.Session)
if s == nil {
return wire.WriteResponse(conn, wire.StatusError, []byte("unknown session"))
}
if len(req.Payload) != 14 {
return wire.WriteResponse(conn, wire.StatusError, []byte("bad download request"))
}
ack := binary.BigEndian.Uint64(req.Payload[0:8])
limit := int(binary.BigEndian.Uint32(req.Payload[8:12]))
count := int(binary.BigEndian.Uint16(req.Payload[12:14]))
if limit < 1 {
limit = 1
}
if limit > maxChunk {
limit = maxChunk
}
if count < 1 {
count = 1
}
if count > 256 {
count = 256
}
s.ack(ack)
offset := req.Seq
if debug != nil && debug.enabled {
debug.pullRequests.Add(1)
}
for i := 0; i < count; i++ {
wait := time.Duration(0)
if i == 0 {
wait = pollWait
}
data, status, err := s.readAt(offset, limit, wait)
if err != nil {
return wire.WriteResponse(conn, wire.StatusError, []byte(err.Error()))
}
switch status {
case wire.StatusData:
if debug != nil && debug.enabled {
debug.dataRecords.Add(1)
}
if err := wire.WriteMaskedResponse(conn, wire.StatusData, data, req.Session, wire.ModeDownload, offset); err != nil {
return err
}
offset += uint64(len(data))
case wire.StatusWait:
if debug != nil && debug.enabled {
debug.waitRecords.Add(1)
}
return wire.WriteResponse(conn, wire.StatusWait, nil)
case wire.StatusEOF:
return wire.WriteResponse(conn, wire.StatusEOF, nil)
default:
return wire.WriteResponse(conn, wire.StatusError, []byte("invalid session read status"))
}
}
return nil
case wire.ModeClose:
manager.remove(req.Session)
return wire.WriteResponse(conn, wire.StatusOK, nil)
default:
return wire.WriteResponse(conn, wire.StatusError, []byte("unknown mode"))
}
}
@@ -0,0 +1,22 @@
package main
import (
"encoding/binary"
"testing"
)
func TestParseOpenAllowsEmptyToken(t *testing.T) {
host := "example.com"
p := make([]byte, 6+len(host))
binary.BigEndian.PutUint16(p[0:2], 0)
binary.BigEndian.PutUint16(p[2:4], uint16(len(host)))
binary.BigEndian.PutUint16(p[4:6], 443)
copy(p[6:], host)
token, gotHost, port, err := parseOpen(p)
if err != nil {
t.Fatal(err)
}
if token != "" || gotHost != host || port != 443 {
t.Fatalf("got token=%q host=%q port=%d", token, gotHost, port)
}
}
@@ -0,0 +1,83 @@
package main
import (
"fmt"
"os"
"sync/atomic"
"time"
)
type serverDebug struct {
enabled bool
chunks bool
statsEvery time.Duration
started time.Time
sessionsOpened atomic.Uint64
sessionsClosed atomic.Uint64
activeSessions atomic.Int64
bytesUp atomic.Uint64
bytesDown atomic.Uint64
pushRecords atomic.Uint64
pullRequests atomic.Uint64
dataRecords atomic.Uint64
waitRecords atomic.Uint64
errors atomic.Uint64
}
func newServerDebug(enabled, chunks bool, statsEvery time.Duration) *serverDebug {
d := &serverDebug{
enabled: enabled || chunks,
chunks: chunks,
statsEvery: statsEvery,
started: time.Now(),
}
if d.enabled && d.statsEvery > 0 {
go d.statsLoop()
}
return d
}
func (d *serverDebug) logf(format string, args ...any) {
if d == nil || !d.enabled {
return
}
fmt.Fprintf(os.Stderr, "%s [DEBUG] "+format+"\n", append([]any{time.Now().Format("2006-01-02 15:04:05.000")}, args...)...)
}
func (d *serverDebug) chunkf(format string, args ...any) {
if d == nil || !d.chunks {
return
}
fmt.Fprintf(os.Stderr, "%s [CHUNK] "+format+"\n", append([]any{time.Now().Format("2006-01-02 15:04:05.000")}, args...)...)
}
func (d *serverDebug) errorf(format string, args ...any) {
if d == nil || !d.enabled {
return
}
d.errors.Add(1)
fmt.Fprintf(os.Stderr, "%s [ERROR] "+format+"\n", append([]any{time.Now().Format("2006-01-02 15:04:05.000")}, args...)...)
}
func (d *serverDebug) statsLoop() {
ticker := time.NewTicker(d.statsEvery)
defer ticker.Stop()
for range ticker.C {
d.logf(
"STATS uptime=%s active_connections=%d active_sessions=%d sessions_opened=%d sessions_closed=%d bytes_up=%d bytes_down=%d push_records=%d pull_requests=%d data_records=%d waits=%d errors=%d",
time.Since(d.started).Round(time.Second),
atomic.LoadInt64(&active),
d.activeSessions.Load(),
d.sessionsOpened.Load(),
d.sessionsClosed.Load(),
d.bytesUp.Load(),
d.bytesDown.Load(),
d.pushRecords.Load(),
d.pullRequests.Load(),
d.dataRecords.Load(),
d.waitRecords.Load(),
d.errors.Load(),
)
}
}
+290
View File
@@ -0,0 +1,290 @@
package main
import (
"context"
"crypto/subtle"
"flag"
"fmt"
"net"
"net/netip"
"os"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"dragontcp/internal/protocol"
"dragontcp/internal/wire"
)
var active int64
type dnsEntry struct {
ips []netip.Addr
expires time.Time
}
type dnsCache struct {
mu sync.RWMutex
entries map[string]dnsEntry
ttl time.Duration
max int
}
func newDNSCache(ttl time.Duration, max int) *dnsCache {
return &dnsCache{
entries: make(map[string]dnsEntry),
ttl: ttl,
max: max,
}
}
func (c *dnsCache) resolve(ctx context.Context, host string) ([]netip.Addr, error) {
if ip, err := netip.ParseAddr(host); err == nil {
return []netip.Addr{ip}, nil
}
now := time.Now()
c.mu.RLock()
entry, ok := c.entries[host]
c.mu.RUnlock()
if ok && now.Before(entry.expires) {
return entry.ips, nil
}
ips, err := net.DefaultResolver.LookupNetIP(ctx, "ip", host)
if err != nil {
return nil, err
}
c.mu.Lock()
if len(c.entries) >= c.max {
// Simple bounded reset keeps the hot cache cheap and prevents growth.
c.entries = make(map[string]dnsEntry, c.max)
}
c.entries[host] = dnsEntry{ips: ips, expires: now.Add(c.ttl)}
c.mu.Unlock()
return ips, nil
}
func tokenEqual(a, b string) bool {
if len(a) != len(b) {
return false
}
return subtle.ConstantTimeCompare([]byte(a), []byte(b)) == 1
}
var blockedSpecial = []netip.Prefix{
netip.MustParsePrefix("0.0.0.0/8"),
netip.MustParsePrefix("100.64.0.0/10"),
netip.MustParsePrefix("192.0.0.0/24"),
netip.MustParsePrefix("192.0.2.0/24"),
netip.MustParsePrefix("198.18.0.0/15"),
netip.MustParsePrefix("198.51.100.0/24"),
netip.MustParsePrefix("203.0.113.0/24"),
netip.MustParsePrefix("240.0.0.0/4"),
netip.MustParsePrefix("2001:db8::/32"),
}
func addressAllowed(addr netip.Addr, allowPrivate bool) bool {
if addr.IsUnspecified() || addr.IsMulticast() {
return false
}
if allowPrivate {
return true
}
if !addr.IsGlobalUnicast() ||
addr.IsPrivate() ||
addr.IsLoopback() ||
addr.IsLinkLocalUnicast() {
return false
}
for _, prefix := range blockedSpecial {
if prefix.Contains(addr) {
return false
}
}
return true
}
func dialTarget(ctx context.Context, host string, port int, allowPrivate bool, cache *dnsCache, tcpBuffer int) (net.Conn, error) {
ips, err := cache.resolve(ctx, host)
if err != nil {
return nil, err
}
var lastErr error
var blocked []string
d := net.Dialer{
Timeout: 10 * time.Second,
KeepAlive: 30 * time.Second,
}
for _, ip := range ips {
if !addressAllowed(ip, allowPrivate) {
blocked = append(blocked, ip.String())
continue
}
addr := net.JoinHostPort(ip.String(), strconv.Itoa(port))
conn, err := d.DialContext(ctx, "tcp", addr)
if err == nil {
protocol.TuneTCP(conn)
protocol.TuneTCPBuffer(conn, tcpBuffer)
return conn, nil
}
lastErr = err
}
if lastErr != nil {
return nil, lastErr
}
if len(blocked) > 0 {
return nil, fmt.Errorf("target resolves only to blocked addresses: %s", strings.Join(blocked, ","))
}
return nil, fmt.Errorf("no usable target address")
}
func handle(
conn net.Conn,
token string,
allowPrivate bool,
cache *dnsCache,
tcpBuffer int,
slots chan struct{},
manager *streamManager,
chunkMax int,
bufferBytes int,
chunkPollWait time.Duration,
debug *serverDebug,
) {
defer func() {
<-slots
atomic.AddInt64(&active, -1)
_ = conn.Close()
}()
protocol.TuneTCP(conn)
protocol.TuneTCPBuffer(conn, tcpBuffer)
for {
_ = conn.SetDeadline(time.Now().Add(30 * time.Second))
req, err := wire.ReadRequest(conn)
if err != nil {
return
}
if err := processWireRequest(
conn,
req,
token,
allowPrivate,
cache,
tcpBuffer,
manager,
chunkMax,
bufferBytes,
chunkPollWait,
debug,
); err != nil {
return
}
}
}
func main() {
var (
host = flag.String("host", "0.0.0.0", "listen host")
port = flag.Int("port", 53, "listen port")
token = flag.String("token", "", "optional shared token")
maxConnections = flag.Int("max-connections", 20000, "max simultaneous tunnels")
allowPrivate = flag.Bool("allow-private", false, "allow private/loopback targets")
dnsCacheTTL = flag.Duration("dns-cache-ttl", 30*time.Second, "server DNS cache TTL")
dnsCacheSize = flag.Int("dns-cache-size", 4096, "maximum cached DNS hostnames")
tcpBuffer = flag.Int("tcp-buffer", 0, "optional TCP read/write buffer bytes; 0 keeps OS autotuning")
chunkMax = flag.Int("chunk-max", 1048576, "maximum adaptive chunk payload bytes (32 bytes to 1 MiB)")
chunkBuffered = flag.Int("chunk-buffered", 256, "compatibility buffer units; 256 = about 16 MiB per active session")
chunkPollWait = flag.Duration("chunk-poll-wait", 200*time.Millisecond, "server long-poll wait for chunk data")
sessionTimeout = flag.Duration("chunk-session-timeout", 2*time.Minute, "idle chunk session timeout")
debugEnabled = flag.Bool("debug", false, "log session/connect/errors and periodic statistics")
debugChunks = flag.Bool("debug-chunks", false, "log every chunk protocol record; very verbose")
debugStats = flag.Duration("debug-stats-interval", 5*time.Second, "periodic debug statistics interval; 0 disables")
)
flag.Parse()
if *chunkMax < 32 || *chunkMax > protocol.MaxChunkPayload {
fmt.Fprintf(os.Stderr, "--chunk-max must be between 32 and %d\n", protocol.MaxChunkPayload)
os.Exit(2)
}
if *chunkBuffered < 8 {
fmt.Fprintln(os.Stderr, "--chunk-buffered must be at least 8")
os.Exit(2)
}
listenAddr := net.JoinHostPort(*host, strconv.Itoa(*port))
ln, err := net.Listen("tcp", listenAddr)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
defer ln.Close()
fmt.Printf("DragonTCP Go server listening on %s\n", listenAddr)
fmt.Printf("max_connections=%d tcp_buffer=%d\n", *maxConnections, *tcpBuffer)
slots := make(chan struct{}, *maxConnections)
cache := newDNSCache(*dnsCacheTTL, *dnsCacheSize)
debug := newServerDebug(*debugEnabled, *debugChunks, *debugStats)
bufferBytes := *chunkBuffered * 65536
if bufferBytes < 1024*1024 {
bufferBytes = 1024 * 1024
}
if bufferBytes > 64*1024*1024 {
bufferBytes = 64 * 1024 * 1024
}
manager := newStreamManager(*sessionTimeout, debug)
fmt.Printf("binary_transport=true chunk_max=%d buffer_bytes=%d poll_wait=%s\n", *chunkMax, bufferBytes, chunkPollWait.String())
if debug.enabled {
fmt.Printf("debug=true debug_chunks=%t stats_interval=%s\n", debug.chunks, debug.statsEvery)
}
for {
conn, err := ln.Accept()
if err != nil {
fmt.Fprintln(os.Stderr, "accept:", err)
continue
}
select {
case slots <- struct{}{}:
atomic.AddInt64(&active, 1)
if debug.enabled {
debug.logf("ACCEPT peer=%v active_connections=%d", conn.RemoteAddr(), atomic.LoadInt64(&active))
}
go handle(
conn,
*token,
*allowPrivate,
cache,
*tcpBuffer,
slots,
manager,
*chunkMax,
bufferBytes,
*chunkPollWait,
debug,
)
default:
if debug.enabled {
debug.errorf("REJECT peer=%v reason=max-connections", conn.RemoteAddr())
}
_ = conn.Close()
}
}
}