@@ -43,10 +43,7 @@ type TCPMetrics interface {
4343 AddOpenTCPConnection (ip net.Addr )
4444 AddAuthenticatedTCPConnection (ip net.Addr , accessKey string )
4545 AddClosedTCPConnection (ip net.Addr , accessKey string , status string , data metrics.ProxyMetrics , duration time.Duration )
46-
47- // Shadowsocks TCP metrics
4846 AddTCPProbe (status , drainResult string , port int , clientProxyBytes int64 )
49- AddTCPCipherSearch (accessKeyFound bool , timeToCipher time.Duration )
5047}
5148
5249func remoteIP (conn net.Conn ) net.IP {
@@ -119,26 +116,67 @@ func findEntry(firstBytes []byte, ciphers []*list.Element) (*CipherEntry, *list.
119116 return nil , nil
120117}
121118
119+ type StreamAuthenticateFunc func (clientConn transport.StreamConn ) (string , transport.StreamConn , * onet.ConnectionError )
120+
121+ // ShadowsocksTCPMetrics is used to report Shadowsocks metrics on TCP connections.
122+ type ShadowsocksTCPMetrics interface {
123+ // Shadowsocks TCP metrics
124+ AddTCPCipherSearch (accessKeyFound bool , timeToCipher time.Duration )
125+ }
126+
127+ // NewShadowsocksStreamAuthenticator creates a stream authenticator that uses Shadowsocks.
128+ // TODO(fortuna): Offer alternative transports.
129+ func NewShadowsocksStreamAuthenticator (ciphers CipherList , replayCache * ReplayCache , metrics ShadowsocksTCPMetrics ) StreamAuthenticateFunc {
130+ return func (clientConn transport.StreamConn ) (string , transport.StreamConn , * onet.ConnectionError ) {
131+ // Find the cipher and acess key id.
132+ cipherEntry , clientReader , clientSalt , timeToCipher , keyErr := findAccessKey (clientConn , remoteIP (clientConn ), ciphers )
133+ metrics .AddTCPCipherSearch (keyErr == nil , timeToCipher )
134+ if keyErr != nil {
135+ const status = "ERR_CIPHER"
136+ return "" , nil , onet .NewConnectionError (status , "Failed to find a valid cipher" , keyErr )
137+ }
138+ var id string
139+ if cipherEntry != nil {
140+ id = cipherEntry .ID
141+ }
142+
143+ // Check if the connection is a replay.
144+ isServerSalt := cipherEntry .SaltGenerator .IsServerSalt (clientSalt )
145+ // Only check the cache if findAccessKey succeeded and the salt is unrecognized.
146+ if isServerSalt || ! replayCache .Add (cipherEntry .ID , clientSalt ) {
147+ var status string
148+ if isServerSalt {
149+ status = "ERR_REPLAY_SERVER"
150+ } else {
151+ status = "ERR_REPLAY_CLIENT"
152+ }
153+ return id , nil , onet .NewConnectionError (status , "Replay detected" , nil )
154+ }
155+
156+ metrics .AddAuthenticatedTCPConnection (clientConn .RemoteAddr (), id )
157+ ssr := shadowsocks .NewReader (clientReader , cipherEntry .CryptoKey )
158+ ssw := shadowsocks .NewWriter (clientConn , cipherEntry .CryptoKey )
159+ ssw .SetSaltGenerator (cipherEntry .SaltGenerator )
160+ return id , transport .WrapConn (clientConn , ssr , ssw ), nil
161+ }
162+ }
163+
122164type tcpHandler struct {
123- port int
124- ciphers CipherList
125- m TCPMetrics
126- readTimeout time.Duration
127- // `replayCache` is a pointer to SSServer.replayCache, to share the cache among all ports.
128- replayCache * ReplayCache
129- dialer transport.StreamDialer
165+ port int
166+ m TCPMetrics
167+ readTimeout time.Duration
168+ authenticate StreamAuthenticateFunc
169+ dialer transport.StreamDialer
130170}
131171
132172// NewTCPService creates a TCPService
133- // `replayCache` is a pointer to SSServer.replayCache, to share the cache among all ports.
134- func NewTCPHandler (port int , ciphers CipherList , replayCache * ReplayCache , m TCPMetrics , timeout time.Duration ) TCPHandler {
173+ func NewTCPHandler (port int , authenticate StreamAuthenticateFunc , m TCPMetrics , timeout time.Duration ) TCPHandler {
135174 return & tcpHandler {
136- port : port ,
137- ciphers : ciphers ,
138- m : m ,
139- readTimeout : timeout ,
140- replayCache : replayCache ,
141- dialer : defaultDialer ,
175+ port : port ,
176+ m : m ,
177+ readTimeout : timeout ,
178+ authenticate : authenticate ,
179+ dialer : defaultDialer ,
142180 }
143181}
144182
@@ -235,42 +273,6 @@ func (h *tcpHandler) Handle(ctx context.Context, clientConn transport.StreamConn
235273 logger .Debugf ("Done with status %v, duration %v" , status , connDuration )
236274}
237275
238- func (h * tcpHandler ) authenticate (clientConn transport.StreamConn , proxyMetrics * metrics.ProxyMetrics ) (string , transport.StreamConn , * onet.ConnectionError ) {
239- // TODO(fortuna): Offer alternative transports.
240- // Find the cipher and acess key id.
241- cipherEntry , clientReader , clientSalt , timeToCipher , keyErr := findAccessKey (clientConn , remoteIP (clientConn ), h .ciphers )
242- h .m .AddTCPCipherSearch (keyErr == nil , timeToCipher )
243- if keyErr != nil {
244- logger .Debugf ("Failed to find a valid cipher after reading %v bytes: %v" , proxyMetrics .ClientProxy , keyErr )
245- const status = "ERR_CIPHER"
246- return "" , nil , onet .NewConnectionError (status , "Failed to find a valid cipher" , keyErr )
247- }
248- var id string
249- if cipherEntry != nil {
250- id = cipherEntry .ID
251- }
252-
253- // Check if the connection is a replay.
254- isServerSalt := cipherEntry .SaltGenerator .IsServerSalt (clientSalt )
255- // Only check the cache if findAccessKey succeeded and the salt is unrecognized.
256- if isServerSalt || ! h .replayCache .Add (cipherEntry .ID , clientSalt ) {
257- var status string
258- if isServerSalt {
259- status = "ERR_REPLAY_SERVER"
260- } else {
261- status = "ERR_REPLAY_CLIENT"
262- }
263- logger .Debugf (status + ": %v sent %d bytes" , clientConn .RemoteAddr (), proxyMetrics .ClientProxy )
264- return id , nil , onet .NewConnectionError (status , "Replay detected" , nil )
265- }
266-
267- h .m .AddAuthenticatedTCPConnection (clientConn .RemoteAddr (), id )
268- ssr := shadowsocks .NewReader (clientReader , cipherEntry .CryptoKey )
269- ssw := shadowsocks .NewWriter (clientConn , cipherEntry .CryptoKey )
270- ssw .SetSaltGenerator (cipherEntry .SaltGenerator )
271- return id , transport .WrapConn (clientConn , ssr , ssw ), nil
272- }
273-
274276func getProxyRequest (clientConn transport.StreamConn ) (string , error ) {
275277 // TODO(fortuna): Use Shadowsocks proxy, HTTP CONNECT or SOCKS5 based on first byte:
276278 // case 1, 3 or 4: Shadowsocks (address type)
@@ -332,7 +334,7 @@ func (h *tcpHandler) handleConnection(ctx context.Context, listenerPort int, out
332334 }
333335 outerConn .SetReadDeadline (readDeadline )
334336
335- id , innerConn , authErr := h .authenticate (outerConn , proxyMetrics )
337+ id , innerConn , authErr := h .authenticate (outerConn )
336338 if authErr != nil {
337339 // Drain to protect against probing attacks.
338340 h .absorbProbe (listenerPort , outerConn , authErr .Status , proxyMetrics )
0 commit comments