Skip to content
This repository was archived by the owner on Apr 9, 2020. It is now read-only.

Commit bf618d1

Browse files
committed
Merge branch 'develop', add OTA support.
2 parents 2b4d9d7 + 0fd1c39 commit bf618d1

File tree

16 files changed

+373
-229
lines changed

16 files changed

+373
-229
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
*.deb
22
script/http
3+
bin

.travis.yml

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
language: go
22
go:
3-
- 1.4.2
3+
- 1.4.3
44
install:
55
- go get golang.org/x/crypto/blowfish
66
- go get golang.org/x/crypto/cast5
@@ -10,3 +10,4 @@ install:
1010
- go install ./cmd/shadowsocks-server
1111
script:
1212
- PATH=$PATH:$HOME/gopath/bin bash -x ./script/test.sh
13+
sudo: false

README.md

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# shadowsocks-go
22

3-
Current version: 1.1.4 [![Build Status](https://travis-ci.org/shadowsocks/shadowsocks-go.png?branch=master)](https://travis-ci.org/shadowsocks/shadowsocks-go)
3+
Current version: 1.1.5 [![Build Status](https://travis-ci.org/shadowsocks/shadowsocks-go.png?branch=master)](https://travis-ci.org/shadowsocks/shadowsocks-go)
44

55
shadowsocks-go is a lightweight tunnel proxy which can help you get through firewalls. It is a port of [shadowsocks](https://github.com/clowwindy/shadowsocks).
66

@@ -12,7 +12,7 @@ The protocol is compatible with the origin shadowsocks (if both have been upgrad
1212

1313
# Install
1414

15-
Compiled client binaries can be download [here](http://dl.chenyufei.info/shadowsocks/). (All compiled with cgo disabled, except the mac version.)
15+
Download precompiled binarys from the [release page](https://github.com/shadowsocks/shadowsocks-go/releases). (All compiled with cgo disabled, except the mac version.)
1616

1717
You can also install from source (assume you have go installed):
1818

@@ -55,6 +55,13 @@ AES is recommended for shadowsocks-go. [Intel AES Instruction Set](http://en.wik
5555

5656
**rc4 and table encryption methods are deprecated because they are not secure.**
5757

58+
### One Time Auth
59+
60+
Append `-auth` to the encryption method to enable [One Time Auth (OTA)](https://shadowsocks.org/en/spec/one-time-auth.html).
61+
62+
- For server: this will **force client use OTA**, non-OTA connection will be dropped. Otherwise, both OTA and non-OTA clients can connect
63+
- For client: the `-A` command line option can also enable OTA
64+
5865
## Command line options
5966

6067
Command line options can override settings from configuration files. Use `-h` option to see all available options.

cmd/shadowsocks-local/local.go

+19-4
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,17 @@ import (
55
"errors"
66
"flag"
77
"fmt"
8-
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks"
98
"io"
109
"log"
1110
"math/rand"
1211
"net"
1312
"os"
1413
"path"
1514
"strconv"
15+
"strings"
1616
"time"
17+
18+
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks"
1719
)
1820

1921
var debug ss.DebugLog
@@ -170,8 +172,12 @@ func parseServerConfig(config *ss.Config) {
170172
}
171173

172174
if len(config.ServerPassword) == 0 {
175+
method := config.Method
176+
if config.Auth {
177+
method += "-auth"
178+
}
173179
// only one encryption table
174-
cipher, err := ss.NewCipher(config.Method, config.Password)
180+
cipher, err := ss.NewCipher(method, config.Password)
175181
if err != nil {
176182
log.Fatal("Failed generating ciphers:", err)
177183
}
@@ -208,14 +214,17 @@ func parseServerConfig(config *ss.Config) {
208214
if !hasPort(server) {
209215
log.Fatalf("no port for server %s\n", server)
210216
}
211-
cipher, ok := cipherCache[passwd]
217+
// Using "|" as delimiter is safe here, since no encryption
218+
// method contains it in the name.
219+
cacheKey := encmethod + "|" + passwd
220+
cipher, ok := cipherCache[cacheKey]
212221
if !ok {
213222
var err error
214223
cipher, err = ss.NewCipher(encmethod, passwd)
215224
if err != nil {
216225
log.Fatal("Failed generating ciphers:", err)
217226
}
218-
cipherCache[passwd] = cipher
227+
cipherCache[cacheKey] = cipher
219228
}
220229
servers.srvCipher[i] = &ServerCipher{server, cipher}
221230
i++
@@ -360,6 +369,7 @@ func main() {
360369
flag.IntVar(&cmdConfig.LocalPort, "l", 0, "local socks5 proxy port")
361370
flag.StringVar(&cmdConfig.Method, "m", "", "encryption method, default: aes-256-cfb")
362371
flag.BoolVar((*bool)(&debug), "d", false, "print debug message")
372+
flag.BoolVar(&cmdConfig.Auth, "A", false, "one time auth")
363373

364374
flag.Parse()
365375

@@ -371,6 +381,11 @@ func main() {
371381
cmdConfig.Server = cmdServer
372382
ss.SetDebug(debug)
373383

384+
if strings.HasSuffix(cmdConfig.Method, "-auth") {
385+
cmdConfig.Method = cmdConfig.Method[:len(cmdConfig.Method)-4]
386+
cmdConfig.Auth = true
387+
}
388+
374389
exists, err := ss.IsFileExists(configFile)
375390
// If no config file in current directory, try search it in the binary directory
376391
// Note there's no portable way to detect the binary directory.

cmd/shadowsocks-server/server.go

+71-53
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,80 @@
11
package main
22

33
import (
4+
"bytes"
45
"encoding/binary"
56
"errors"
67
"flag"
78
"fmt"
8-
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks"
99
"io"
1010
"log"
1111
"net"
1212
"os"
1313
"os/signal"
1414
"runtime"
1515
"strconv"
16+
"strings"
1617
"sync"
1718
"syscall"
19+
20+
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks"
1821
)
1922

20-
var debug ss.DebugLog
23+
const (
24+
idType = 0 // address type index
25+
idIP0 = 1 // ip addres start index
26+
idDmLen = 1 // domain address length index
27+
idDm0 = 2 // domain address start index
2128

22-
func getRequest(conn *ss.Conn) (host string, extra []byte, err error) {
23-
const (
24-
idType = 0 // address type index
25-
idIP0 = 1 // ip addres start index
26-
idDmLen = 1 // domain address length index
27-
idDm0 = 2 // domain address start index
29+
typeIPv4 = 1 // type is ipv4 address
30+
typeDm = 3 // type is domain address
31+
typeIPv6 = 4 // type is ipv6 address
2832

29-
typeIPv4 = 1 // type is ipv4 address
30-
typeDm = 3 // type is domain address
31-
typeIPv6 = 4 // type is ipv6 address
33+
lenIPv4 = net.IPv4len + 2 // ipv4 + 2port
34+
lenIPv6 = net.IPv6len + 2 // ipv6 + 2port
35+
lenDmBase = 2 // 1addrLen + 2port, plus addrLen
36+
lenHmacSha1 = 10
37+
)
3238

33-
lenIPv4 = 1 + net.IPv4len + 2 // 1addrType + ipv4 + 2port
34-
lenIPv6 = 1 + net.IPv6len + 2 // 1addrType + ipv6 + 2port
35-
lenDmBase = 1 + 1 + 2 // 1addrType + 1addrLen + 2port, plus addrLen
36-
)
39+
var debug ss.DebugLog
40+
41+
func getRequest(conn *ss.Conn, auth bool) (host string, ota bool, err error) {
42+
ss.SetReadTimeout(conn)
3743

3844
// buf size should at least have the same size with the largest possible
3945
// request size (when addrType is 3, domain name has at most 256 bytes)
40-
// 1(addrType) + 1(lenByte) + 256(max length address) + 2(port)
41-
buf := make([]byte, 260)
42-
var n int
46+
// 1(addrType) + 1(lenByte) + 256(max length address) + 2(port) + 10(hmac-sha1)
47+
buf := make([]byte, 270)
4348
// read till we get possible domain length field
44-
ss.SetReadTimeout(conn)
45-
if n, err = io.ReadAtLeast(conn, buf, idDmLen+1); err != nil {
49+
if _, err = io.ReadFull(conn, buf[:idType+1]); err != nil {
4650
return
4751
}
4852

49-
reqLen := -1
50-
switch buf[idType] {
53+
var reqStart, reqEnd int
54+
addrType := buf[idType]
55+
switch addrType & ss.AddrMask {
5156
case typeIPv4:
52-
reqLen = lenIPv4
57+
reqStart, reqEnd = idIP0, idIP0+lenIPv4
5358
case typeIPv6:
54-
reqLen = lenIPv6
59+
reqStart, reqEnd = idIP0, idIP0+lenIPv6
5560
case typeDm:
56-
reqLen = int(buf[idDmLen]) + lenDmBase
61+
if _, err = io.ReadFull(conn, buf[idType+1:idDmLen+1]); err != nil {
62+
return
63+
}
64+
reqStart, reqEnd = idDm0, int(idDm0+buf[idDmLen]+lenDmBase)
5765
default:
58-
err = fmt.Errorf("addr type %d not supported", buf[idType])
66+
err = fmt.Errorf("addr type %d not supported", addrType&ss.AddrMask)
5967
return
6068
}
6169

62-
if n < reqLen { // rare case
63-
if _, err = io.ReadFull(conn, buf[n:reqLen]); err != nil {
64-
return
65-
}
66-
} else if n > reqLen {
67-
// it's possible to read more than just the request head
68-
extra = buf[reqLen:n]
70+
if _, err = io.ReadFull(conn, buf[reqStart:reqEnd]); err != nil {
71+
return
6972
}
7073

7174
// Return string for typeIP is not most efficient, but browsers (Chrome,
7275
// Safari, Firefox) all seems using typeDm exclusively. So this is not a
7376
// big problem.
74-
switch buf[idType] {
77+
switch addrType & ss.AddrMask {
7578
case typeIPv4:
7679
host = net.IP(buf[idIP0 : idIP0+net.IPv4len]).String()
7780
case typeIPv6:
@@ -80,8 +83,22 @@ func getRequest(conn *ss.Conn) (host string, extra []byte, err error) {
8083
host = string(buf[idDm0 : idDm0+buf[idDmLen]])
8184
}
8285
// parse port
83-
port := binary.BigEndian.Uint16(buf[reqLen-2 : reqLen])
86+
port := binary.BigEndian.Uint16(buf[reqEnd-2 : reqEnd])
8487
host = net.JoinHostPort(host, strconv.Itoa(int(port)))
88+
// if specified one time auth enabled, we should verify this
89+
if auth || addrType&ss.OneTimeAuthMask > 0 {
90+
ota = true
91+
if _, err = io.ReadFull(conn, buf[reqEnd:reqEnd+lenHmacSha1]); err != nil {
92+
return
93+
}
94+
iv := conn.GetIv()
95+
key := conn.GetKey()
96+
actualHmacSha1Buf := ss.HmacSha1(append(iv, key...), buf[:reqEnd])
97+
if !bytes.Equal(buf[reqEnd:reqEnd+lenHmacSha1], actualHmacSha1Buf) {
98+
err = fmt.Errorf("verify one time auth failed, iv=%v key=%v data=%v", iv, key, buf[:reqEnd])
99+
return
100+
}
101+
}
85102
return
86103
}
87104

@@ -90,7 +107,7 @@ const logCntDelta = 100
90107
var connCnt int
91108
var nextLogConnCnt int = logCntDelta
92109

93-
func handleConnection(conn *ss.Conn) {
110+
func handleConnection(conn *ss.Conn, auth bool) {
94111
var host string
95112

96113
connCnt++ // this maybe not accurate, but should be enough
@@ -118,7 +135,7 @@ func handleConnection(conn *ss.Conn) {
118135
}
119136
}()
120137

121-
host, extra, err := getRequest(conn)
138+
host, ota, err := getRequest(conn, auth)
122139
if err != nil {
123140
log.Println("error getting request", conn.RemoteAddr(), conn.LocalAddr(), err)
124141
return
@@ -140,18 +157,14 @@ func handleConnection(conn *ss.Conn) {
140157
remote.Close()
141158
}
142159
}()
143-
// write extra bytes read from
144-
if extra != nil {
145-
// debug.Println("getRequest read extra data, writing to remote, len", len(extra))
146-
if _, err = remote.Write(extra); err != nil {
147-
debug.Println("write request extra error:", err)
148-
return
149-
}
150-
}
151160
if debug {
152-
debug.Printf("piping %s<->%s", conn.RemoteAddr(), host)
161+
debug.Printf("piping %s<->%s ota=%v connOta=%v", conn.RemoteAddr(), host, ota, conn.IsOta())
162+
}
163+
if ota {
164+
go ss.PipeThenCloseOta(conn, remote)
165+
} else {
166+
go ss.PipeThenClose(conn, remote)
153167
}
154-
go ss.PipeThenClose(conn, remote)
155168
ss.PipeThenClose(remote, conn)
156169
closed = true
157170
return
@@ -195,7 +208,7 @@ func (pm *PasswdManager) del(port string) {
195208
// port. A different approach would be directly change the password used by
196209
// that port, but that requires **sharing** password between the port listener
197210
// and password manager.
198-
func (pm *PasswdManager) updatePortPasswd(port, password string) {
211+
func (pm *PasswdManager) updatePortPasswd(port, password string, auth bool) {
199212
pl, ok := pm.get(port)
200213
if !ok {
201214
log.Printf("new port %s added\n", port)
@@ -208,7 +221,7 @@ func (pm *PasswdManager) updatePortPasswd(port, password string) {
208221
}
209222
// run will add the new port listener to passwdManager.
210223
// So there maybe concurrent access to passwdManager and we need lock to protect it.
211-
go run(port, password)
224+
go run(port, password, auth)
212225
}
213226

214227
var passwdManager = PasswdManager{portListener: map[string]*PortListener{}}
@@ -227,7 +240,7 @@ func updatePasswd() {
227240
return
228241
}
229242
for port, passwd := range config.PortPassword {
230-
passwdManager.updatePortPasswd(port, passwd)
243+
passwdManager.updatePortPasswd(port, passwd, config.Auth)
231244
if oldconfig.PortPassword != nil {
232245
delete(oldconfig.PortPassword, port)
233246
}
@@ -254,7 +267,7 @@ func waitSignal() {
254267
}
255268
}
256269

257-
func run(port, password string) {
270+
func run(port, password string, auth bool) {
258271
ln, err := net.Listen("tcp", ":"+port)
259272
if err != nil {
260273
log.Printf("error listening port %v: %v\n", port, err)
@@ -280,7 +293,7 @@ func run(port, password string) {
280293
continue
281294
}
282295
}
283-
go handleConnection(ss.NewConn(conn, cipher.Copy()))
296+
go handleConnection(ss.NewConn(conn, cipher.Copy()), auth)
284297
}
285298
}
286299

@@ -332,6 +345,11 @@ func main() {
332345

333346
ss.SetDebug(debug)
334347

348+
if strings.HasSuffix(cmdConfig.Method, "-auth") {
349+
cmdConfig.Method = cmdConfig.Method[:len(cmdConfig.Method)-4]
350+
cmdConfig.Auth = true
351+
}
352+
335353
var err error
336354
config, err = ss.ParseConfig(configFile)
337355
if err != nil {
@@ -357,7 +375,7 @@ func main() {
357375
runtime.GOMAXPROCS(core)
358376
}
359377
for port, password := range config.PortPassword {
360-
go run(port, password)
378+
go run(port, password, config.Auth)
361379
}
362380

363381
waitSignal()

config.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33
"server_port":8388,
44
"local_port":1080,
55
"password":"barfoo!",
6-
"method": "aes-128-cfb",
6+
"method": "aes-128-cfb-auth",
77
"timeout":600
88
}

script/build.sh

+2-2
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,13 @@ build linux 386 linux32 local
4949
build windows amd64 win64 local
5050
build windows 386 win32 local
5151

52+
#build darwin amd64 mac64 server
5253
build linux amd64 linux64 server
5354
build linux 386 linux32 server
54-
build darwin amd64 mac64 server
5555
build windows amd64 win64 server
5656
build windows 386 win32 server
5757

5858
#script/createdeb.sh amd64
59-
#script/createdeb.sh i386
59+
#script/createdeb.sh 386
6060
#mv shadowsocks-go_$version-1-*.deb bin/
6161
#rm -rf shadowsocks-go_$version-1*

0 commit comments

Comments
 (0)