1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package proxy
import (
"errors"
"io"
"net"
"sync"
"time"
"github.com/armanlife/firefox-vpn-client/core/internal/logx"
)
const (
copyBufferSize = 64 * 1024
halfCloseDrainTimeout = 2 * time.Minute
)
var copyBufferPool = sync.Pool{
New: func() any {
return make([]byte, copyBufferSize)
},
}
func proxyBidirectional(clientConn, upstreamConn net.Conn, idleTimeout time.Duration) {
var activity func()
done := make(chan struct{})
if idleTimeout > 0 {
activityCh := make(chan struct{}, 1)
activity = func() {
select {
case activityCh <- struct{}{}:
default:
}
}
go closeIdleConns(clientConn, upstreamConn, idleTimeout, activityCh, done)
} else {
activity = func() {}
}
results := make(chan error, 2)
go func() {
err := copyConn(upstreamConn, clientConn, activity)
if err == nil {
closeWrite(upstreamConn)
}
results <- err
}()
go func() {
err := copyConn(clientConn, upstreamConn, activity)
if err == nil {
closeWrite(clientConn)
}
results <- err
}()
firstErr := <-results
if firstErr != nil {
_ = clientConn.Close()
_ = upstreamConn.Close()
}
var secondErr error
if firstErr == nil {
drainTimer := time.NewTimer(halfCloseDrainTimeout)
select {
case secondErr = <-results:
if !drainTimer.Stop() {
<-drainTimer.C
}
case <-drainTimer.C:
logx.Debug("closing half-closed tunnel after drain timeout=%s", halfCloseDrainTimeout)
_ = clientConn.Close()
_ = upstreamConn.Close()
secondErr = <-results
}
} else {
secondErr = <-results
}
if secondErr != nil {
_ = clientConn.Close()
_ = upstreamConn.Close()
}
close(done)
}
func closeIdleConns(clientConn, upstreamConn net.Conn, idleTimeout time.Duration, activity <-chan struct{}, done <-chan struct{}) {
timer := time.NewTimer(idleTimeout)
defer timer.Stop()
for {
select {
case <-activity:
if !timer.Stop() {
select {
case <-timer.C:
default:
}
}
timer.Reset(idleTimeout)
case <-timer.C:
_ = clientConn.Close()
_ = upstreamConn.Close()
return
case <-done:
return
}
}
}
func copyConn(dst, src net.Conn, activity func()) error {
buf := copyBufferPool.Get().([]byte)
defer copyBufferPool.Put(buf)
for {
nr, readErr := src.Read(buf)
if nr > 0 {
activity()
nw, writeErr := dst.Write(buf[:nr])
if nw > 0 {
activity()
}
if writeErr != nil {
return writeErr
}
if nw != nr {
return io.ErrShortWrite
}
}
if readErr != nil {
if errors.Is(readErr, io.EOF) {
return nil
}
return readErr
}
}
}
func closeWrite(conn net.Conn) {
if closeWriter, ok := conn.(interface{ CloseWrite() error }); ok {
_ = closeWriter.CloseWrite()
}
}