├── README.md ├── bin ├── config.json ├── lightsocks-linux32 ├── lightsocks-linux64 ├── lightsocks-win32.exe └── lightsocks-win64.exe └── src ├── config.go ├── config.json ├── crypt.go ├── main.go └── worker.go /README.md: -------------------------------------------------------------------------------- 1 | # lightsocks-go 2 | lightsocks client implements by Golang. 3 | -------------------------------------------------------------------------------- /bin/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "server_ip": "0.0.0.0", 3 | "server_port": 8888, 4 | "password": "Password!01", 5 | "method": "aes-cfb-128", 6 | "local_ip": "0.0.0.0", 7 | "local_port": 1080 8 | } -------------------------------------------------------------------------------- /bin/lightsocks-linux32: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lightsocks/lightsocks-go/987a4610ce83259066cbff64bfa983a128d4b54b/bin/lightsocks-linux32 -------------------------------------------------------------------------------- /bin/lightsocks-linux64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lightsocks/lightsocks-go/987a4610ce83259066cbff64bfa983a128d4b54b/bin/lightsocks-linux64 -------------------------------------------------------------------------------- /bin/lightsocks-win32.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lightsocks/lightsocks-go/987a4610ce83259066cbff64bfa983a128d4b54b/bin/lightsocks-win32.exe -------------------------------------------------------------------------------- /bin/lightsocks-win64.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lightsocks/lightsocks-go/987a4610ce83259066cbff64bfa983a128d4b54b/bin/lightsocks-win64.exe -------------------------------------------------------------------------------- /src/config.go: -------------------------------------------------------------------------------- 1 | // config 2 | package main 3 | 4 | import ( 5 | "encoding/json" 6 | "io/ioutil" 7 | "os" 8 | ) 9 | 10 | type CrptorParam struct { 11 | keyLen int 12 | ivLen int 13 | cryptType string 14 | } 15 | 16 | type Config struct { 17 | ServerIp string `json:"server_ip"` 18 | ServerPort int `json:"server_port"` 19 | LocalIp string `json:"local_ip"` 20 | LocalPort int `json:"local_port"` 21 | Password string `json:"password"` 22 | Method string `json:"method"` 23 | CrptorParam *CrptorParam 24 | } 25 | 26 | func parseConfig(configFile string) (config *Config, err error) { 27 | file, err := os.Open(configFile) // For read access. 28 | if err != nil { 29 | return 30 | } 31 | defer file.Close() 32 | 33 | data, err := ioutil.ReadAll(file) 34 | if err != nil { 35 | return 36 | } 37 | config = &Config{} 38 | err = json.Unmarshal(data, config) 39 | return 40 | } 41 | 42 | func(config *Config) checkConifg() bool { 43 | if config.ServerIp == "" { 44 | return false 45 | } 46 | if config.ServerPort == 0 { 47 | return false 48 | } 49 | if config.LocalIp == "" { 50 | return false 51 | } 52 | if config.LocalPort == 0 { 53 | return false 54 | } 55 | if config.Password == "" { 56 | return false 57 | } 58 | if config.Method == "" { 59 | return false 60 | } 61 | return true 62 | } 63 | -------------------------------------------------------------------------------- /src/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "server_ip": "0.0.0.0", 3 | "server_port": 8888, 4 | "password": "Password!01", 5 | "method": "aes-cfb-128", 6 | "local_ip": "0.0.0.0", 7 | "local_port": 1080 8 | } -------------------------------------------------------------------------------- /src/crypt.go: -------------------------------------------------------------------------------- 1 | // crypt 2 | package main 3 | 4 | import ( 5 | "crypto/aes" 6 | "crypto/cipher" 7 | "crypto/md5" 8 | "crypto/rand" 9 | "io" 10 | ) 11 | 12 | type DecOrEnc int 13 | 14 | const ( 15 | Decrypt DecOrEnc = iota 16 | Encrypt 17 | ) 18 | 19 | func newModelStream(key, iv []byte, cryptType string, doe DecOrEnc) (cipher.Stream, error) { 20 | block, err := newCipher(key, cryptType) 21 | return newStream(block, err, key, iv, doe) 22 | } 23 | 24 | func newCipher(key []byte, cryptType string) (cipher.Block, error) { 25 | if cryptType == "AES" { 26 | return aes.NewCipher(key) 27 | } 28 | return aes.NewCipher(key) 29 | } 30 | 31 | func newStream(block cipher.Block, err error, key, iv []byte, 32 | doe DecOrEnc) (cipher.Stream, error) { 33 | if err != nil { 34 | return nil, err 35 | } 36 | if doe == Encrypt { 37 | return cipher.NewCFBEncrypter(block, iv), nil 38 | } else { 39 | return cipher.NewCFBDecrypter(block, iv), nil 40 | } 41 | } 42 | 43 | func initIV(ivLen int) (iv []byte, err error) { 44 | iv = make([]byte, ivLen) 45 | if _, err := io.ReadFull(rand.Reader, iv); err != nil { 46 | return nil, err 47 | } 48 | return 49 | } 50 | 51 | func md5sum(d []byte) []byte { 52 | h := md5.New() 53 | h.Write(d) 54 | return h.Sum(nil) 55 | } 56 | 57 | func evpBytesToKey(password string, keyLen int) (key []byte) { 58 | const md5Len = 16 59 | 60 | cnt := (keyLen-1)/md5Len + 1 61 | m := make([]byte, cnt*md5Len) 62 | copy(m, md5sum([]byte(password))) 63 | d := make([]byte, md5Len+len(password)) 64 | start := 0 65 | for i := 1; i < cnt; i++ { 66 | start += md5Len 67 | copy(d, m[start-md5Len:start]) 68 | copy(d[md5Len:], password) 69 | copy(m[start:], md5sum(d)) 70 | } 71 | return m[:keyLen] 72 | } 73 | 74 | var aes1 = CrptorParam{16, 16, "AES"} 75 | 76 | func getCrptorParam(method string) *CrptorParam { 77 | if method == "aes-cfb-128" { 78 | return &aes1 79 | } 80 | return &aes1 81 | } 82 | -------------------------------------------------------------------------------- /src/main.go: -------------------------------------------------------------------------------- 1 | // lightsocks project main.go 2 | package main 3 | 4 | import ( 5 | "flag" 6 | "log" 7 | "net" 8 | "strconv" 9 | ) 10 | 11 | var config Config 12 | 13 | func main() { 14 | var configFile string 15 | flag.StringVar(&configFile, "-c", "config.json", "specify config file") 16 | myconfig, err := parseConfig(configFile) 17 | if err != nil { 18 | log.Println("parse config error:", err) 19 | } 20 | flag.StringVar(&config.ServerIp, "-s", myconfig.ServerIp, "server address") 21 | flag.IntVar(&config.ServerPort, "-p", myconfig.ServerPort, "server port") 22 | flag.StringVar(&config.Password, "-k", myconfig.Password, "password") 23 | flag.StringVar(&config.Method, "-m", myconfig.Method, "encryption method, default: aes-128-cfb") 24 | flag.StringVar(&config.LocalIp, "-b", myconfig.LocalIp, "local address") 25 | flag.IntVar(&config.LocalPort, "-l", myconfig.LocalPort, "local port") 26 | 27 | var help string 28 | flag.StringVar(&help, "-h", "1", "help") 29 | flag.Parse() 30 | if help != "1" { 31 | flag.PrintDefaults() 32 | return 33 | } 34 | if !(&config).checkConifg() { 35 | return 36 | } 37 | config.CrptorParam = getCrptorParam(config.Method) 38 | listen(config.LocalIp, config.LocalPort) 39 | } 40 | 41 | func listen(localAddress string, port int) { 42 | var address = localAddress + ":" + strconv.Itoa(port) 43 | ln, err := net.Listen("tcp", address) 44 | if err != nil { 45 | // handle error 46 | } 47 | log.Println("proxy is ready ,address:", address) 48 | 49 | for { 50 | conn, err := ln.Accept() 51 | if err != nil { 52 | // handle error 53 | } 54 | go handleConnection(conn) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/worker.go: -------------------------------------------------------------------------------- 1 | // Worker 2 | package main 3 | 4 | import ( 5 | "crypto/cipher" 6 | "encoding/binary" 7 | "errors" 8 | "io" 9 | "log" 10 | "net" 11 | "strconv" 12 | ) 13 | 14 | var ( 15 | errAddrType = errors.New("socks addr type not supported") 16 | errVer = errors.New("socks version not supported") 17 | errMethod = errors.New("socks only support 1 method now") 18 | errAuthExtraData = errors.New("socks authentication get extra data") 19 | errReqExtraData = errors.New("socks request get extra data") 20 | errCmd = errors.New("socks command not supported") 21 | ) 22 | 23 | const ( 24 | socksVer5 = 5 25 | socksCmdConnect = 1 26 | ) 27 | 28 | func handleConnection(conn net.Conn) { 29 | var err error = nil 30 | if err = handShake(conn); err != nil { 31 | log.Fatalln("socks handshake:", err) 32 | return 33 | } 34 | rawaddr, addr, err := getRequest(conn) 35 | 36 | log.Println("target server address:", addr) 37 | 38 | if err != nil { 39 | log.Println("send connection confirmation:", err) 40 | return 41 | } 42 | remote, err := createServerConn() 43 | 44 | if err != nil { 45 | log.Println("send connection confirmation:", err) 46 | return 47 | } 48 | 49 | go localToRemote(conn, remote, rawaddr) 50 | remoteToLocal(remote, conn) 51 | } 52 | 53 | func handShake(conn net.Conn) (err error) { 54 | const ( 55 | idVer = 0 56 | idNmethod = 1 57 | ) 58 | // version identification and method selection message in theory can have 59 | // at most 256 methods, plus version and nmethod field in total 258 bytes 60 | // the current rfc defines only 3 authentication methods (plus 2 reserved), 61 | // so it won't be such long in practice 62 | 63 | buf := make([]byte, 258) 64 | 65 | var n int 66 | // make sure we get the nmethod field 67 | if n, err = io.ReadAtLeast(conn, buf, idNmethod+1); err != nil { 68 | return 69 | } 70 | if buf[idVer] != socksVer5 { 71 | return errVer 72 | } 73 | nmethod := int(buf[idNmethod]) 74 | msgLen := nmethod + 2 75 | if n == msgLen { // handshake done, common case 76 | // do nothing, jump directly to send confirmation 77 | } else if n < msgLen { // has more methods to read, rare case 78 | if _, err = io.ReadFull(conn, buf[n:msgLen]); err != nil { 79 | return 80 | } 81 | } else { // error, should not get extra data 82 | return errAuthExtraData 83 | } 84 | // send confirmation: version 5, no authentication required 85 | _, err = conn.Write([]byte{socksVer5, 0}) 86 | return 87 | } 88 | 89 | func getRequest(conn net.Conn) (rawaddr []byte, host string, err error) { 90 | const ( 91 | idVer = 0 92 | idCmd = 1 93 | idType = 3 // address type index 94 | idIP0 = 4 // ip addres start index 95 | idDmLen = 4 // domain address length index 96 | idDm0 = 5 // domain address start index 97 | 98 | typeIPv4 = 1 // type is ipv4 address 99 | typeDm = 3 // type is domain address 100 | typeIPv6 = 4 // type is ipv6 address 101 | 102 | lenIPv4 = 3 + 1 + net.IPv4len + 2 // 3(ver+cmd+rsv) + 1addrType + ipv4 + 2port 103 | lenIPv6 = 3 + 1 + net.IPv6len + 2 // 3(ver+cmd+rsv) + 1addrType + ipv6 + 2port 104 | lenDmBase = 3 + 1 + 1 + 2 // 3 + 1addrType + 1addrLen + 2port, plus addrLen 105 | ) 106 | // refer to getRequest in server.go for why set buffer size to 263 107 | buf := make([]byte, 263) 108 | var n int 109 | // read till we get possible domain length field 110 | if n, err = io.ReadAtLeast(conn, buf, idDmLen+1); err != nil { 111 | return 112 | } 113 | // check version and cmd 114 | if buf[idVer] != socksVer5 { 115 | err = errVer 116 | return 117 | } 118 | if buf[idCmd] != socksCmdConnect { 119 | err = errCmd 120 | return 121 | } 122 | 123 | reqLen := -1 124 | switch buf[idType] { 125 | case typeIPv4: 126 | reqLen = lenIPv4 127 | case typeIPv6: 128 | reqLen = lenIPv6 129 | case typeDm: 130 | reqLen = int(buf[idDmLen]) + lenDmBase 131 | default: 132 | err = errAddrType 133 | return 134 | } 135 | 136 | if n == reqLen { 137 | // common case, do nothing 138 | } else if n < reqLen { // rare case 139 | if _, err = io.ReadFull(conn, buf[n:reqLen]); err != nil { 140 | return 141 | } 142 | } else { 143 | err = errReqExtraData 144 | return 145 | } 146 | 147 | rawaddr = buf[idType:reqLen] 148 | 149 | switch buf[idType] { 150 | case typeIPv4: 151 | host = net.IP(buf[idIP0 : idIP0+net.IPv4len]).String() 152 | case typeIPv6: 153 | host = net.IP(buf[idIP0 : idIP0+net.IPv6len]).String() 154 | case typeDm: 155 | host = string(buf[idDm0 : idDm0+buf[idDmLen]]) 156 | } 157 | port := binary.BigEndian.Uint16(buf[reqLen-2 : reqLen]) 158 | host = net.JoinHostPort(host, strconv.Itoa(int(port))) 159 | return 160 | } 161 | 162 | func createServerConn() (remote net.Conn, err error) { 163 | remote, err = net.Dial("tcp", config.ServerIp+":"+strconv.Itoa(config.ServerPort)) 164 | if err != nil { 165 | return nil, err 166 | } 167 | return remote, err 168 | } 169 | 170 | func localToRemote(conn net.Conn, remote net.Conn, rawaddr []byte) { 171 | defer remote.Close() 172 | //send iv 173 | iv, err := initIV(config.CrptorParam.ivLen) 174 | if err != nil { 175 | return 176 | } 177 | key := evpBytesToKey(config.Password, config.CrptorParam.keyLen) 178 | 179 | head := make([]byte, 4) 180 | binary.BigEndian.PutUint32(head, uint32(config.CrptorParam.ivLen)) 181 | if _, err := remote.Write(head); err != nil { 182 | return 183 | } 184 | if _, err := remote.Write(head); err != nil { 185 | return 186 | } 187 | if _, err := remote.Write(iv); err != nil { 188 | return 189 | } 190 | 191 | stream, err := newModelStream(key, iv, config.CrptorParam.cryptType, Encrypt) 192 | if err != nil { 193 | return 194 | } 195 | //send address 196 | validate := len(rawaddr) 197 | if validate > 16 { 198 | padding := 16 - validate%16 199 | total := validate + padding 200 | address := make([]byte, total) 201 | copy(address, rawaddr) 202 | if _, err := forwardData(remote, address, stream); err != nil { 203 | return 204 | } 205 | } else { 206 | if _, err := forwardData(remote, rawaddr, stream); err != nil { 207 | return 208 | } 209 | } 210 | 211 | buf := make([]byte, 4096) 212 | for { 213 | n, err := conn.Read(buf) 214 | // read may return EOF with n > 0 215 | // should always process n > 0 bytes before handling error 216 | if n > 0 { 217 | // Note: avoid overwrite err returned by Read. 218 | if _, err := forwardData(remote, buf[0:n], stream); err != nil { 219 | break 220 | } 221 | } 222 | if err != nil { 223 | break 224 | } 225 | } 226 | } 227 | 228 | func remoteToLocal(remote net.Conn, conn net.Conn) { 229 | defer conn.Close() 230 | head := make([]byte, 4) 231 | if _, err := io.ReadAtLeast(remote, head, 4); err != nil { 232 | return 233 | } 234 | if _, err := io.ReadAtLeast(remote, head, 4); err != nil { 235 | return 236 | } 237 | iv := make([]byte, config.CrptorParam.ivLen) 238 | if _, err := io.ReadAtLeast(remote, iv, config.CrptorParam.ivLen); err != nil { 239 | return 240 | } 241 | key := evpBytesToKey(config.Password, config.CrptorParam.keyLen) 242 | stream, err := newModelStream(key, iv, config.CrptorParam.cryptType, Decrypt) 243 | if err != nil { 244 | return 245 | } 246 | _, err = conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x08, 0x43}) 247 | if err != nil { 248 | return 249 | } 250 | 251 | for { 252 | if _, err := io.ReadAtLeast(remote, head, 4); err != nil { 253 | break 254 | } 255 | validate := binary.BigEndian.Uint32(head) 256 | if _, err := io.ReadAtLeast(remote, head, 4); err != nil { 257 | break 258 | } 259 | length := binary.BigEndian.Uint32(head) 260 | src := make([]byte, length) 261 | dst := make([]byte, length) 262 | n, err := io.ReadAtLeast(remote, src, int(length)) 263 | stream.XORKeyStream(dst, src) 264 | // read may return EOF with n > 0 265 | // should always process n > 0 bytes before handling error 266 | if n > 0 { 267 | // Note: avoid overwrite err returned by Read. 268 | if _, err := conn.Write(dst[0:validate]); err != nil { 269 | break 270 | } 271 | } 272 | if err != nil { 273 | break 274 | } 275 | } 276 | } 277 | 278 | func forwardData(conn net.Conn, data []byte, stream cipher.Stream) (n int, err error) { 279 | left := len(data) % 16 280 | first := len(data) - left 281 | n1 := 0 282 | if first != 0 { 283 | dst := make([]byte, 8+first) 284 | binary.BigEndian.PutUint32(dst, uint32(first)) 285 | binary.BigEndian.PutUint32(dst[4:], uint32(first)) 286 | stream.XORKeyStream(dst[8:], data[0:first]) 287 | n1, err = conn.Write(dst) 288 | if err != nil { 289 | return n1, err 290 | } 291 | } 292 | n2 := 0 293 | if left != 0 { 294 | src := make([]byte, 16) 295 | dst := make([]byte, 8+16) 296 | copy(src, data[first:]) 297 | binary.BigEndian.PutUint32(dst, uint32(left)) 298 | binary.BigEndian.PutUint32(dst[4:], uint32(16)) 299 | stream.XORKeyStream(dst[8:], src) 300 | n2, err = conn.Write(dst) 301 | if err != nil { 302 | return n2, err 303 | } 304 | } 305 | return n1 + n2, err 306 | } 307 | --------------------------------------------------------------------------------