• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ooni / probe-cli / 13234378528

10 Feb 2025 05:42AM UTC coverage: 71.865% (-0.01%) from 71.877%
13234378528

Pull #1688

github

DecFox
chore: update toolchain to go1.22.3
Pull Request #1688: chore: update toolchain to go1.22.3

28107 of 39111 relevant lines covered (71.86%)

46.83 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

60.71
/internal/ptx/ptx.go
1
package ptx
2

3
/*-
4
         This file is derived from client/snowflake.go
5
    in git.torproject.org/pluggable-transports/snowflake.git
6
                whose license is the following:
7

8
================================================================================
9

10
Copyright (c) 2016, Serene Han, Arlo Breault
11
Copyright (c) 2019-2020, The Tor Project, Inc
12

13
Redistribution and use in source and binary forms, with or without modification,
14
are permitted provided that the following conditions are met:
15

16
  * Redistributions of source code must retain the above copyright notice, this
17
list of conditions and the following disclaimer.
18

19
  * Redistributions in binary form must reproduce the above copyright notice,
20
this list of conditions and the following disclaimer in the documentation and/or
21
other materials provided with the distribution.
22

23
  * Neither the names of the copyright owners nor the names of its
24
contributors may be used to endorse or promote products derived from this
25
software without specific prior written permission.
26

27
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
28
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
29
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
30
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
31
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
32
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
33
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
34
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
35
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
36
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37
================================================================================
38
*/
39

40
import (
41
        "context"
42
        "errors"
43
        "fmt"
44
        "net"
45
        "strings"
46
        "sync"
47

48
        "github.com/ooni/probe-cli/v3/internal/bytecounter"
49
        "github.com/ooni/probe-cli/v3/internal/model"
50
        "github.com/ooni/probe-cli/v3/internal/netxlite"
51
        pt "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/goptlib"
52
)
53

54
// PTDialer is a generic pluggable transports dialer.
55
type PTDialer interface {
56
        // DialContext establishes a connection to the pluggable
57
        // transport backend according to PT-specific configuration
58
        // and returns you such a connection.
59
        DialContext(ctx context.Context) (net.Conn, error)
60

61
        // AsBridgeArgument returns the argument to be passed to
62
        // the tor command line to declare this bridge.
63
        AsBridgeArgument() string
64

65
        // Name returns the pluggable transport name.
66
        Name() string
67
}
68

69
// Listener is a generic pluggable transports listener. Make sure
70
// you fill the mandatory fields before using it. Do not modify public
71
// fields after you called Start, since this causes data races.
72
type Listener struct {
73
        // ExperimentByteCounter is the OPTIONAL byte counter that
74
        // counts the bytes consumed by the experiment.
75
        ExperimentByteCounter *bytecounter.Counter
76

77
        // ListenSocks is OPTIONAL and allows you to override the
78
        // function called by default to listen for SOCKS5.
79
        ListenSocks func(network string, laddr string) (SocksListener, error)
80

81
        // Logger is the OPTIONAL logger. When not set, this library
82
        // will not emit logs. (But the underlying pluggable transport
83
        // may still emit its own log messages.)
84
        Logger model.Logger
85

86
        // PTDialer is the MANDATORY pluggable transports dialer
87
        // to use. Both SnowflakeDialer and OBFS4Dialer implement this
88
        // interface and can be thus safely used here.
89
        PTDialer PTDialer
90

91
        // SessionByteCounter is the OPTIONAL byte counter that
92
        // counts the bytes consumed by the session.
93
        SessionByteCounter *bytecounter.Counter
94

95
        // mu provides mutual exclusion for accessing internals.
96
        mu sync.Mutex
97

98
        // cancel allows stopping the forwarders.
99
        cancel context.CancelFunc
100

101
        // laddr is the listen address.
102
        laddr net.Addr
103

104
        // listener allows us to stop the listener.
105
        listener SocksListener
106
}
107

108
// logger returns the Logger, if set, or the defaultLogger.
109
func (lst *Listener) logger() model.Logger {
3✔
110
        if lst.Logger != nil {
4✔
111
                return lst.Logger
1✔
112
        }
1✔
113
        return model.DiscardLogger
2✔
114
}
115

116
// forward forwards the traffic from left to right and from right to left
117
// and closes the done channel when it is done. This function DOES NOT
118
// take ownership of the left, right net.Conn arguments.
119
func (lst *Listener) forward(ctx context.Context, left, right net.Conn, done chan struct{}) {
2✔
120
        defer close(done) // signal termination
2✔
121
        wg := new(sync.WaitGroup)
2✔
122
        wg.Add(2)
2✔
123
        go func() {
4✔
124
                defer wg.Done()
2✔
125
                _, _ = netxlite.CopyContext(ctx, left, right)
2✔
126
        }()
2✔
127
        go func() {
4✔
128
                defer wg.Done()
2✔
129
                _, _ = netxlite.CopyContext(ctx, right, left)
2✔
130
        }()
2✔
131
        wg.Wait()
2✔
132
}
133

134
// forwardWithContext forwards the traffic from left to right and
135
// form right to left, interrupting when the context is done. This
136
// function TAKES OWNERSHIP of the two connections and ensures
137
// that they are closed when we are done.
138
func (lst *Listener) forwardWithContext(ctx context.Context, left, right net.Conn) {
2✔
139
        defer left.Close()
2✔
140
        defer right.Close()
2✔
141
        done := make(chan struct{})
2✔
142
        go lst.forward(ctx, left, right, done)
2✔
143
        select {
2✔
144
        case <-ctx.Done():
1✔
145
        case <-done:
×
146
        }
147
}
148

149
// handleSocksConn handles a new SocksConn connection by establishing
150
// the corresponding PT connection and forwarding traffic. This
151
// function TAKES OWNERSHIP of the socksConn argument.
152
func (lst *Listener) handleSocksConn(ctx context.Context, socksConn SocksConn) error {
2✔
153
        err := socksConn.Grant(&net.TCPAddr{IP: net.IPv4zero, Port: 0})
2✔
154
        if err != nil {
3✔
155
                lst.logger().Warnf("ptx: socksConn.Grant error: %s", err)
1✔
156
                return err // used for testing
1✔
157
        }
1✔
158
        ptConn, err := lst.PTDialer.DialContext(ctx)
1✔
159
        if err != nil {
2✔
160
                _ = socksConn.Close() // we own it
1✔
161
                lst.logger().Warnf("ptx: ContextDialer.DialContext error: %s", err)
1✔
162
                return err // used for testing
1✔
163
        }
1✔
164
        // We _must_ wrap the ptConn. Wrapping the socks conn leads us to
165
        // count the sent bytes as received and the received bytes as sent:
166
        // bytes flow in the opposite direction there for the socks conn.
167
        ptConn = bytecounter.MaybeWrapConn(ptConn, lst.SessionByteCounter)
×
168
        ptConn = bytecounter.MaybeWrapConn(ptConn, lst.ExperimentByteCounter)
×
169
        lst.forwardWithContext(ctx, socksConn, ptConn) // transfer ownership
×
170
        return nil                                     // used for testing
×
171
}
172

173
// SocksListener is the listener for socks connections.
174
type SocksListener interface {
175
        // AcceptSocks accepts a socks conn
176
        AcceptSocks() (SocksConn, error)
177

178
        // Addr returns the listening address.
179
        Addr() net.Addr
180

181
        // Close closes the listener
182
        Close() error
183
}
184

185
// SocksConn is a SOCKS connection.
186
type SocksConn interface {
187
        // net.Conn is the embedded interface.
188
        net.Conn
189

190
        // Grant grants access to a specific IP address.
191
        Grant(addr *net.TCPAddr) error
192
}
193

194
// acceptLoop accepts and handles local socks connection. This function
195
// DOES NOT take ownership of the socks listener.
196
func (lst *Listener) acceptLoop(ctx context.Context, ln SocksListener) {
1✔
197
        for {
104✔
198
                conn, err := ln.AcceptSocks()
103✔
199
                if err != nil {
205✔
200
                        if err, ok := err.(net.Error); ok && err.Temporary() {
204✔
201
                                continue
102✔
202
                        }
203
                        if !errors.Is(err, net.ErrClosed) {
×
204
                                lst.logger().Warnf("ptx: socks accept error: %s", err)
×
205
                        }
×
206
                        return
×
207
                }
208
                go lst.handleSocksConn(ctx, conn)
×
209
        }
210
}
211

212
// Addr returns the listening address. This function should not
213
// be called after you have called the Stop method or before the
214
// Start method has successfully returned. When invoked in such
215
// conditions, this function may return nil. Otherwise, it will
216
// return the valid net.Addr where we are listening.
217
func (lst *Listener) Addr() net.Addr {
×
218
        return lst.laddr
×
219
}
×
220

221
// Start starts the pluggable transport Listener. The pluggable transport will
222
// run in a background goroutine until txp.Stop is called. Attempting to
223
// call Start when the pluggable transport is already running is a
224
// no-op causing no error and no data races.
225
func (lst *Listener) Start() error {
1✔
226
        lst.mu.Lock()
1✔
227
        defer lst.mu.Unlock()
1✔
228
        if lst.cancel != nil {
1✔
229
                return nil // already started
×
230
        }
×
231
        // TODO(bassosimone): be able to recover when SOCKS dies?
232
        ln, err := lst.listenSocks("tcp", "127.0.0.1:0")
1✔
233
        if err != nil {
2✔
234
                return err
1✔
235
        }
1✔
236
        lst.laddr = ln.Addr()
×
237
        ctx, cancel := context.WithCancel(context.Background())
×
238
        lst.cancel = cancel
×
239
        lst.listener = ln
×
240
        go lst.acceptLoop(ctx, ln)
×
241
        lst.logger().Infof("ptx: started socks listener at %v", ln.Addr())
×
242
        lst.logger().Debugf("ptx: test with `%s`", lst.torCmdLine())
×
243
        return nil
×
244
}
245

246
// listenSocks calles either pt.ListenSocks or lst.overrideListenSocks.
247
func (lst *Listener) listenSocks(network string, laddr string) (SocksListener, error) {
1✔
248
        if lst.ListenSocks != nil {
2✔
249
                return lst.ListenSocks(network, laddr)
1✔
250
        }
1✔
251
        return lst.castListener(pt.ListenSocks(network, laddr))
×
252
}
253

254
// castListener casts a pt.SocksListener to ptxSocksListener.
255
func (lst *Listener) castListener(in *pt.SocksListener, err error) (SocksListener, error) {
1✔
256
        if err != nil {
2✔
257
                return nil, err
1✔
258
        }
1✔
259
        return &ptxSocksListenerAdapter{in}, nil
×
260
}
261

262
// ptxSocksListenerAdapter adapts pt.SocksListener to ptxSocksListener.
263
type ptxSocksListenerAdapter struct {
264
        *pt.SocksListener
265
}
266

267
// AcceptSocks adapts pt.SocksListener.AcceptSocks to ptxSockListener.AcceptSocks.
268
func (la *ptxSocksListenerAdapter) AcceptSocks() (SocksConn, error) {
×
269
        return la.SocksListener.AcceptSocks()
×
270
}
×
271

272
// torCmdLine prints the command line for testing this listener. This method is here to
273
// facilitate debugging with `ptxclient`, so there is no need to be too precise with arguments
274
// quoting. Remember to improve upon this aspect if you plan on using it beyond testing.
275
func (lst *Listener) torCmdLine() string {
×
276
        return strings.Join([]string{
×
277
                "tor",
×
278
                "DataDirectory",
×
279
                "testdata",
×
280
                "UseBridges",
×
281
                "1",
×
282
                "ClientTransportPlugin",
×
283
                "'" + lst.AsClientTransportPluginArgument() + "'",
×
284
                "Bridge",
×
285
                "'" + lst.PTDialer.AsBridgeArgument() + "'",
×
286
        }, " ")
×
287
}
×
288

289
// Stop stops the pluggable transport. This method is idempotent
290
// and asks the background goroutine(s) to stop just once. Also, this
291
// method is safe to call from any goroutine.
292
func (lst *Listener) Stop() {
1✔
293
        defer lst.mu.Unlock()
1✔
294
        lst.mu.Lock()
1✔
295
        if lst.cancel != nil {
2✔
296
                lst.cancel() // cancel is idempotent
1✔
297
        }
1✔
298
        if lst.listener != nil {
2✔
299
                _ = lst.listener.Close() // should be idempotent
1✔
300
        }
1✔
301
}
302

303
// AsClientTransportPluginArgument converts the current configuration
304
// of the pluggable transport to a ClientTransportPlugin argument to be
305
// passed to the tor daemon command line. This function must be
306
// called after Start and before Stop so that we have a valid Addr.
307
//
308
// Assuming that we are listening at 127.0.0.1:12345, then this
309
// function will return the following string:
310
//
311
//        obfs4 socks5 127.0.0.1:12345
312
//
313
// The correct configuration line for the `torrc` would be:
314
//
315
//        ClientTransportPlugin obfs4 socks5 127.0.0.1:12345
316
//
317
// Since we pass configuration to tor using the command line, it
318
// is more convenient to us to avoid including ClientTransportPlugin
319
// in the returned string. In fact, ClientTransportPlugin and its
320
// arguments need to be two consecutive argv strings.
321
func (lst *Listener) AsClientTransportPluginArgument() string {
×
322
        return fmt.Sprintf("%s socks5 %s", lst.PTDialer.Name(), lst.laddr.String())
×
323
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc