package main
import (
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"flag"
"fmt"
"io"
"log"
"net"
"strconv"
"time"
"github.com/shadowsocks/go-shadowsocks2/core"
)
var password = flag.String(“password”, “”, “password”) var port = flag.Int(“port”, 8388, “server port”)
func main() {
flag.Parse()
if *password == "" {
log.Fatalln("Error: password cannot be empty")
}
serverAddr := fmt.Sprintf(":%d", *port)
listen, err := net.Listen("tcp", serverAddr)
if err != nil {
log.Fatalln(err)
}
defer listen.Close()
log.Printf("Server started on port %d with password %s\n", *port, *password)
for {
conn, err := listen.Accept()
if err != nil {
log.Println(err)
continue
}
go handleConn(conn, *password)
}
}
func handleConn(conn net.Conn, password string) {
defer conn.Close()
buf := make([]byte, 2048)
n, err := conn.Read(buf)
if err != nil {
log.Println(err)
return
}
req, err := parseRequest(buf[:n])
if err != nil {
log.Println(err)
return
}
// Generate a random IV
iv := make([]byte, 16)
_, err = io.ReadFull(rand.Reader, iv)
if err != nil {
log.Println(err)
return
}
block, _ := core.PBKDF2([]byte(password), req.Key, 4096, 32, sha256.New)
stream := core.NewStream(block, iv)
// Write response
resp := make([]byte, 0)
resp = append(resp, byte(len(iv)))
resp = append(resp, iv...)
conn.Write(resp)
// Start data transfer
streamConn := core.NewConn(conn, stream)
defer streamConn.Close()
log.Printf("%s <-> %s\n", conn.RemoteAddr().String(), conn.LocalAddr().String())
// Copy traffic until one side closes the connection
go func() {
defer streamConn.CloseWrite()
io.Copy(streamConn, conn)
}()
go func() {
defer streamConn.CloseRead()
io.Copy(conn, streamConn)
}()
// Wait for connection to close
select {
case <-time.After(time.Hour * 24 * 365):
// never timeout
}
}
func parseRequest(buf []byte) (*core.Request, error) {
req := &core.Request{}
if len(buf) < 2 {
return nil, fmt.Errorf("invalid request")
}
req.Version = buf[0]
req.Cmd = buf[1]
switch req.Cmd {
case core.CmdConnect:
if len(buf) < 4 {
return nil, fmt.Errorf("invalid request")
}
req.AddressType = buf[3]
var addr []byte
switch req.AddressType {
case core.AddrTypeIPv4:
if len(buf) < 7 {
return nil, fmt.Errorf("invalid request")
}
addr = buf[4:7]
case core.AddrTypeIPv6:
if len(buf) < 19 {
return nil, fmt.Errorf("invalid request")
}
addr = buf[4:19]
case core.AddrTypeDomain:
l := int(buf[4])
if len(buf) < 5+l+2 {
return nil, fmt.Errorf("invalid request")
}
addr = buf[5 : 5+l]
req.Port = strconv.Itoa(int(buf[5+l])<<8 | int(buf[5+l+1]))
default:
return nil, fmt.Errorf("unsupported address type")
}
req.Address = net.IP(addr).String()
case core.CmdUDPAssociate:
return nil, fmt.Errorf("unsupported command")
default:
return nil, fmt.Errorf("unsupported command")
}
if req.Cmd == core.CmdConnect && req.AddressType != core.AddrTypeDomain {
portBytes := buf[len(buf)-2:]
req.Port = strconv.Itoa(int(portBytes[0])<<8 | int(portBytes[1]))
}
return req, nil
}
func init() {
log.SetFlags(log.LstdFlags | log.Lshortfile)
}
func getPassword() string {
password := os.Getenv("SS_PASSWORD")
if password == "" {
password = "mysecretpassword"
}
return password
}
func getPort() int {
portStr := os.Getenv("SS_PORT")
if portStr == "" {
portStr = "8388"
}
port, err := strconv.Atoi(portStr)
if err != nil {
panic(err)
}
return port
}
func main() {
password := getPassword()
port := getPort()
serverAddr := fmt.Sprintf(":%d", port)
listen, err := net.Listen("tcp", serverAddr)
if err != nil {
panic(err)
}
defer listen.Close()
log.Printf("Server started on port %d with password %s\n", port, password)
for {
conn, err := listen.Accept()
if err != nil {
panic(err)
}
go handleConn(conn, password)
}
}