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

Commit

Permalink
Merge branch 'develop', add OTA support.
Browse files Browse the repository at this point in the history
  • Loading branch information
cyfdecyf committed May 4, 2016
2 parents 2b4d9d7 + 0fd1c39 commit bf618d1
Show file tree
Hide file tree
Showing 16 changed files with 373 additions and 229 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
*.deb
script/http
bin
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
language: go
go:
- 1.4.2
- 1.4.3
install:
- go get golang.org/x/crypto/blowfish
- go get golang.org/x/crypto/cast5
Expand All @@ -10,3 +10,4 @@ install:
- go install ./cmd/shadowsocks-server
script:
- PATH=$PATH:$HOME/gopath/bin bash -x ./script/test.sh
sudo: false
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# shadowsocks-go

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

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).

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

# Install

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

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

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

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

### One Time Auth

Append `-auth` to the encryption method to enable [One Time Auth (OTA)](https://shadowsocks.org/en/spec/one-time-auth.html).

- For server: this will **force client use OTA**, non-OTA connection will be dropped. Otherwise, both OTA and non-OTA clients can connect
- For client: the `-A` command line option can also enable OTA

## Command line options

Command line options can override settings from configuration files. Use `-h` option to see all available options.
Expand Down
23 changes: 19 additions & 4 deletions cmd/shadowsocks-local/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@ import (
"errors"
"flag"
"fmt"
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks"
"io"
"log"
"math/rand"
"net"
"os"
"path"
"strconv"
"strings"
"time"

ss "github.com/shadowsocks/shadowsocks-go/shadowsocks"
)

var debug ss.DebugLog
Expand Down Expand Up @@ -170,8 +172,12 @@ func parseServerConfig(config *ss.Config) {
}

if len(config.ServerPassword) == 0 {
method := config.Method
if config.Auth {
method += "-auth"
}
// only one encryption table
cipher, err := ss.NewCipher(config.Method, config.Password)
cipher, err := ss.NewCipher(method, config.Password)
if err != nil {
log.Fatal("Failed generating ciphers:", err)
}
Expand Down Expand Up @@ -208,14 +214,17 @@ func parseServerConfig(config *ss.Config) {
if !hasPort(server) {
log.Fatalf("no port for server %s\n", server)
}
cipher, ok := cipherCache[passwd]
// Using "|" as delimiter is safe here, since no encryption
// method contains it in the name.
cacheKey := encmethod + "|" + passwd
cipher, ok := cipherCache[cacheKey]
if !ok {
var err error
cipher, err = ss.NewCipher(encmethod, passwd)
if err != nil {
log.Fatal("Failed generating ciphers:", err)
}
cipherCache[passwd] = cipher
cipherCache[cacheKey] = cipher
}
servers.srvCipher[i] = &ServerCipher{server, cipher}
i++
Expand Down Expand Up @@ -360,6 +369,7 @@ func main() {
flag.IntVar(&cmdConfig.LocalPort, "l", 0, "local socks5 proxy port")
flag.StringVar(&cmdConfig.Method, "m", "", "encryption method, default: aes-256-cfb")
flag.BoolVar((*bool)(&debug), "d", false, "print debug message")
flag.BoolVar(&cmdConfig.Auth, "A", false, "one time auth")

flag.Parse()

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

if strings.HasSuffix(cmdConfig.Method, "-auth") {
cmdConfig.Method = cmdConfig.Method[:len(cmdConfig.Method)-4]
cmdConfig.Auth = true
}

exists, err := ss.IsFileExists(configFile)
// If no config file in current directory, try search it in the binary directory
// Note there's no portable way to detect the binary directory.
Expand Down
124 changes: 71 additions & 53 deletions cmd/shadowsocks-server/server.go
Original file line number Diff line number Diff line change
@@ -1,77 +1,80 @@
package main

import (
"bytes"
"encoding/binary"
"errors"
"flag"
"fmt"
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks"
"io"
"log"
"net"
"os"
"os/signal"
"runtime"
"strconv"
"strings"
"sync"
"syscall"

ss "github.com/shadowsocks/shadowsocks-go/shadowsocks"
)

var debug ss.DebugLog
const (
idType = 0 // address type index
idIP0 = 1 // ip addres start index
idDmLen = 1 // domain address length index
idDm0 = 2 // domain address start index

func getRequest(conn *ss.Conn) (host string, extra []byte, err error) {
const (
idType = 0 // address type index
idIP0 = 1 // ip addres start index
idDmLen = 1 // domain address length index
idDm0 = 2 // domain address start index
typeIPv4 = 1 // type is ipv4 address
typeDm = 3 // type is domain address
typeIPv6 = 4 // type is ipv6 address

typeIPv4 = 1 // type is ipv4 address
typeDm = 3 // type is domain address
typeIPv6 = 4 // type is ipv6 address
lenIPv4 = net.IPv4len + 2 // ipv4 + 2port
lenIPv6 = net.IPv6len + 2 // ipv6 + 2port
lenDmBase = 2 // 1addrLen + 2port, plus addrLen
lenHmacSha1 = 10
)

lenIPv4 = 1 + net.IPv4len + 2 // 1addrType + ipv4 + 2port
lenIPv6 = 1 + net.IPv6len + 2 // 1addrType + ipv6 + 2port
lenDmBase = 1 + 1 + 2 // 1addrType + 1addrLen + 2port, plus addrLen
)
var debug ss.DebugLog

func getRequest(conn *ss.Conn, auth bool) (host string, ota bool, err error) {
ss.SetReadTimeout(conn)

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

reqLen := -1
switch buf[idType] {
var reqStart, reqEnd int
addrType := buf[idType]
switch addrType & ss.AddrMask {
case typeIPv4:
reqLen = lenIPv4
reqStart, reqEnd = idIP0, idIP0+lenIPv4
case typeIPv6:
reqLen = lenIPv6
reqStart, reqEnd = idIP0, idIP0+lenIPv6
case typeDm:
reqLen = int(buf[idDmLen]) + lenDmBase
if _, err = io.ReadFull(conn, buf[idType+1:idDmLen+1]); err != nil {
return
}
reqStart, reqEnd = idDm0, int(idDm0+buf[idDmLen]+lenDmBase)
default:
err = fmt.Errorf("addr type %d not supported", buf[idType])
err = fmt.Errorf("addr type %d not supported", addrType&ss.AddrMask)
return
}

if n < reqLen { // rare case
if _, err = io.ReadFull(conn, buf[n:reqLen]); err != nil {
return
}
} else if n > reqLen {
// it's possible to read more than just the request head
extra = buf[reqLen:n]
if _, err = io.ReadFull(conn, buf[reqStart:reqEnd]); err != nil {
return
}

// Return string for typeIP is not most efficient, but browsers (Chrome,
// Safari, Firefox) all seems using typeDm exclusively. So this is not a
// big problem.
switch buf[idType] {
switch addrType & ss.AddrMask {
case typeIPv4:
host = net.IP(buf[idIP0 : idIP0+net.IPv4len]).String()
case typeIPv6:
Expand All @@ -80,8 +83,22 @@ func getRequest(conn *ss.Conn) (host string, extra []byte, err error) {
host = string(buf[idDm0 : idDm0+buf[idDmLen]])
}
// parse port
port := binary.BigEndian.Uint16(buf[reqLen-2 : reqLen])
port := binary.BigEndian.Uint16(buf[reqEnd-2 : reqEnd])
host = net.JoinHostPort(host, strconv.Itoa(int(port)))
// if specified one time auth enabled, we should verify this
if auth || addrType&ss.OneTimeAuthMask > 0 {
ota = true
if _, err = io.ReadFull(conn, buf[reqEnd:reqEnd+lenHmacSha1]); err != nil {
return
}
iv := conn.GetIv()
key := conn.GetKey()
actualHmacSha1Buf := ss.HmacSha1(append(iv, key...), buf[:reqEnd])
if !bytes.Equal(buf[reqEnd:reqEnd+lenHmacSha1], actualHmacSha1Buf) {
err = fmt.Errorf("verify one time auth failed, iv=%v key=%v data=%v", iv, key, buf[:reqEnd])
return
}
}
return
}

Expand All @@ -90,7 +107,7 @@ const logCntDelta = 100
var connCnt int
var nextLogConnCnt int = logCntDelta

func handleConnection(conn *ss.Conn) {
func handleConnection(conn *ss.Conn, auth bool) {
var host string

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

host, extra, err := getRequest(conn)
host, ota, err := getRequest(conn, auth)
if err != nil {
log.Println("error getting request", conn.RemoteAddr(), conn.LocalAddr(), err)
return
Expand All @@ -140,18 +157,14 @@ func handleConnection(conn *ss.Conn) {
remote.Close()
}
}()
// write extra bytes read from
if extra != nil {
// debug.Println("getRequest read extra data, writing to remote, len", len(extra))
if _, err = remote.Write(extra); err != nil {
debug.Println("write request extra error:", err)
return
}
}
if debug {
debug.Printf("piping %s<->%s", conn.RemoteAddr(), host)
debug.Printf("piping %s<->%s ota=%v connOta=%v", conn.RemoteAddr(), host, ota, conn.IsOta())
}
if ota {
go ss.PipeThenCloseOta(conn, remote)
} else {
go ss.PipeThenClose(conn, remote)
}
go ss.PipeThenClose(conn, remote)
ss.PipeThenClose(remote, conn)
closed = true
return
Expand Down Expand Up @@ -195,7 +208,7 @@ func (pm *PasswdManager) del(port string) {
// port. A different approach would be directly change the password used by
// that port, but that requires **sharing** password between the port listener
// and password manager.
func (pm *PasswdManager) updatePortPasswd(port, password string) {
func (pm *PasswdManager) updatePortPasswd(port, password string, auth bool) {
pl, ok := pm.get(port)
if !ok {
log.Printf("new port %s added\n", port)
Expand All @@ -208,7 +221,7 @@ func (pm *PasswdManager) updatePortPasswd(port, password string) {
}
// run will add the new port listener to passwdManager.
// So there maybe concurrent access to passwdManager and we need lock to protect it.
go run(port, password)
go run(port, password, auth)
}

var passwdManager = PasswdManager{portListener: map[string]*PortListener{}}
Expand All @@ -227,7 +240,7 @@ func updatePasswd() {
return
}
for port, passwd := range config.PortPassword {
passwdManager.updatePortPasswd(port, passwd)
passwdManager.updatePortPasswd(port, passwd, config.Auth)
if oldconfig.PortPassword != nil {
delete(oldconfig.PortPassword, port)
}
Expand All @@ -254,7 +267,7 @@ func waitSignal() {
}
}

func run(port, password string) {
func run(port, password string, auth bool) {
ln, err := net.Listen("tcp", ":"+port)
if err != nil {
log.Printf("error listening port %v: %v\n", port, err)
Expand All @@ -280,7 +293,7 @@ func run(port, password string) {
continue
}
}
go handleConnection(ss.NewConn(conn, cipher.Copy()))
go handleConnection(ss.NewConn(conn, cipher.Copy()), auth)
}
}

Expand Down Expand Up @@ -332,6 +345,11 @@ func main() {

ss.SetDebug(debug)

if strings.HasSuffix(cmdConfig.Method, "-auth") {
cmdConfig.Method = cmdConfig.Method[:len(cmdConfig.Method)-4]
cmdConfig.Auth = true
}

var err error
config, err = ss.ParseConfig(configFile)
if err != nil {
Expand All @@ -357,7 +375,7 @@ func main() {
runtime.GOMAXPROCS(core)
}
for port, password := range config.PortPassword {
go run(port, password)
go run(port, password, config.Auth)
}

waitSignal()
Expand Down
2 changes: 1 addition & 1 deletion config.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
"server_port":8388,
"local_port":1080,
"password":"barfoo!",
"method": "aes-128-cfb",
"method": "aes-128-cfb-auth",
"timeout":600
}
4 changes: 2 additions & 2 deletions script/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,13 @@ build linux 386 linux32 local
build windows amd64 win64 local
build windows 386 win32 local

#build darwin amd64 mac64 server
build linux amd64 linux64 server
build linux 386 linux32 server
build darwin amd64 mac64 server
build windows amd64 win64 server
build windows 386 win32 server

#script/createdeb.sh amd64
#script/createdeb.sh i386
#script/createdeb.sh 386
#mv shadowsocks-go_$version-1-*.deb bin/
#rm -rf shadowsocks-go_$version-1*
Loading

0 comments on commit bf618d1

Please sign in to comment.