forked from fortuna/ss-example
-
Notifications
You must be signed in to change notification settings - Fork 191
/
Copy pathtcp.go
139 lines (128 loc) · 5.2 KB
/
tcp.go
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
// Copyright 2018 Jigsaw Operations LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"bytes"
"errors"
"fmt"
"io"
"log"
"net"
"time"
"github.com/Jigsaw-Code/outline-ss-server/metrics"
onet "github.com/Jigsaw-Code/outline-ss-server/net"
"github.com/shadowsocks/go-shadowsocks2/shadowaead"
"github.com/shadowsocks/go-shadowsocks2/socks"
)
func findAccessKey(clientConn onet.DuplexConn, cipherList map[string]shadowaead.Cipher) (string, onet.DuplexConn, error) {
if len(cipherList) == 0 {
return "", nil, errors.New("Empty cipher list")
} else if len(cipherList) == 1 {
for id, cipher := range cipherList {
reader := shadowaead.NewShadowsocksReader(clientConn, cipher)
writer := shadowaead.NewShadowsocksWriter(clientConn, cipher)
return id, onet.WrapConn(clientConn, reader, writer), nil
}
}
// replayBuffer saves the bytes read from shadowConn, in order to allow for replays.
var replayBuffer bytes.Buffer
// Try each cipher until we find one that authenticates successfully.
// This assumes that all ciphers are AEAD.
// TODO: Reorder list to try previously successful ciphers first for the client IP.
// TODO: Ban and log client IPs with too many failures too quick to protect against DoS.
for id, cipher := range cipherList {
log.Printf("Trying key %v", id)
// tmpReader reads first from the replayBuffer and then from clientConn if it needs more
// bytes. All bytes read from clientConn are saved in replayBuffer for future replays.
tmpReader := io.MultiReader(bytes.NewReader(replayBuffer.Bytes()), io.TeeReader(clientConn, &replayBuffer))
cipherReader := shadowaead.NewShadowsocksReader(tmpReader, cipher)
// Read should read just enough data to authenticate the payload size.
_, err := cipherReader.Read(make([]byte, 0))
if err != nil {
log.Printf("Failed key %v: %v", id, err)
continue
}
log.Printf("Selected key %v", id)
// We don't need to keep storing and replaying the bytes anymore, but we don't want to drop
// those already read into the replayBuffer.
ssr := shadowaead.NewShadowsocksReader(io.MultiReader(&replayBuffer, clientConn), cipher)
ssw := shadowaead.NewShadowsocksWriter(clientConn, cipher)
return id, onet.WrapConn(clientConn, ssr, ssw).(onet.DuplexConn), nil
}
return "", nil, fmt.Errorf("could not find valid key")
}
func runTCPService(listener *net.TCPListener, ciphers *map[string]shadowaead.Cipher, m metrics.ShadowsocksMetrics) {
for {
var clientConn onet.DuplexConn
clientConn, err := listener.AcceptTCP()
if err != nil {
log.Printf("failed to accept: %v", err)
continue
}
m.AddOpenTCPConnection()
go func() (connError *connectionError) {
defer func() {
if r := recover(); r != nil {
log.Printf("ERROR Panic in TCP handler: %v", r)
}
}()
connStart := time.Now()
clientConn.(*net.TCPConn).SetKeepAlive(true)
keyID := ""
var proxyMetrics metrics.ProxyMetrics
clientConn = metrics.MeasureConn(clientConn, &proxyMetrics.ProxyClient, &proxyMetrics.ClientProxy)
defer func() {
connEnd := time.Now()
connDuration := connEnd.Sub(connStart)
clientConn.Close()
status := "OK"
if connError != nil {
log.Printf("ERROR [TCP] %v: %v", connError.message, connError.cause)
status = connError.status
}
log.Printf("Done with status %v, duration %v", status, connDuration)
m.AddClosedTCPConnection(keyID, status, proxyMetrics, connDuration)
}()
keyID, clientConn, err := findAccessKey(clientConn, *ciphers)
if err != nil {
return &connectionError{"ERR_CIPHER", "Failed to find a valid cipher", err}
}
tgtAddr, err := socks.ReadAddr(clientConn)
if err != nil {
return &connectionError{"ERR_READ_ADDRESS", "Failed to get target address", err}
}
tgtTCPAddr, err := net.ResolveTCPAddr("tcp", tgtAddr.String())
if err != nil {
return &connectionError{"ERR_RESOLVE_ADDRESS", fmt.Sprintf("Failed to resolve target address %v", tgtAddr.String()), err}
}
if !tgtTCPAddr.IP.IsGlobalUnicast() {
return &connectionError{"ERR_ADDRESS_INVALID", fmt.Sprintf("Target address is not global unicast: %v", tgtAddr.String()), err}
}
tgtTCPConn, err := net.DialTCP("tcp", nil, tgtTCPAddr)
if err != nil {
return &connectionError{"ERR_CONNECT", "Failed to connect to target", err}
}
defer tgtTCPConn.Close()
tgtTCPConn.SetKeepAlive(true)
tgtConn := metrics.MeasureConn(tgtTCPConn, &proxyMetrics.ProxyTarget, &proxyMetrics.TargetProxy)
// TODO: Disable logging in production. This is sensitive.
log.Printf("proxy %s <-> %s", clientConn.RemoteAddr().String(), tgtConn)
_, _, err = onet.Relay(clientConn, tgtConn)
if err != nil {
return &connectionError{"ERR_RELAY", "Failed to relay traffic", err}
}
return nil
}()
}
}