diff --git a/.devcontainer.json b/.devcontainer.json new file mode 100644 index 00000000..75b8759c --- /dev/null +++ b/.devcontainer.json @@ -0,0 +1,12 @@ +{ + "customizations": { + "vscode": { + "extensions": [ + "mkhl.direnv" + ] + } + }, + "image": "ghcr.io/cachix/devenv:latest", + "overrideCommand": false, + "updateContentCommand": "devenv ci" +} diff --git a/.envrc b/.envrc new file mode 100644 index 00000000..6de8a8ac --- /dev/null +++ b/.envrc @@ -0,0 +1,3 @@ +source_url "https://raw.githubusercontent.com/cachix/devenv/d1f7b48e35e6dee421cfd0f51481d17f77586997/direnvrc" "sha256-YBzqskFZxmNb3kYVoKD9ZixoPXJh1C9ZvTLGFRkauZ0=" + +use devenv \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..e407d29c --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,27 @@ +name: 'Test' + +on: + pull_request: + push: + +jobs: + tests: + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v4 + - uses: cachix/install-nix-action@v26 + - uses: cachix/cachix-action@v14 + with: + name: devenv + - name: Install devenv.sh + run: nix profile install --accept-flake-config tarball+https://install.devenv.sh/latest + + - name: Build the devenv shell and run any pre-commit hooks + run: devenv ci + + - name: Run a single command in the devenv shell + run: devenv shell bun run test.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..0fe3a028 --- /dev/null +++ b/.gitignore @@ -0,0 +1,72 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Typescript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# next.js build output +.next + +# Devenv +.devenv* +devenv.local.nix + +# direnv +.direnv + +# pre-commit +.pre-commit-config.yaml + diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..4e897cb9 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,3 @@ +*.yml +*.yaml +.devcontainer.json \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..7f5dba43 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,4 @@ +{ + "bracketSpacing": false, + "singleQuote": true +} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 77637270..00000000 --- a/.travis.yml +++ /dev/null @@ -1,11 +0,0 @@ -language: node_js -node_js: - - "0.8" - - "0.6" -before_install: - - npm install -g coffee-script - - cd shadowsocks - - cp test/config.json . -script: - - cake build - - cake test diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..1e3525d8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) +Copyright (c) 2014-2018 Zhao Xiaohong +Copyright (c) 2012-2014 clowwindy + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE +OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Procfile b/Procfile new file mode 100644 index 00000000..063b78f4 --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: npm start diff --git a/README.md b/README.md index 7bcf65df..db52111e 100644 --- a/README.md +++ b/README.md @@ -1,53 +1,76 @@ -shadowsocks-dotcloud -=========== +# shadowsocks-heroku -[![Build Status](https://travis-ci.org/clowwindy/shadowsocks-dotcloud.png)](https://travis-ci.org/clowwindy/shadowsocks-dotcloud) +shadowsocks-heroku is a lightweight tunnel proxy which can help you get through firewalls. It is a port of [shadowsocks](https://github.com/clowwindy/shadowsocks), but through a different protocol. -shadowsocks-dotcloud is a lightweight tunnel proxy which can help you get through - firewalls. It is a port of [shadowsocks](https://github.com/clowwindy/shadowsocks), but - through a different protocol. +shadowsocks-heroku uses WebSocket instead of raw sockets, so it can be deployed on [Heroku](https://www.heroku.com/). -shadowsocks-dotcloud uses WebSockets instead of raw sockets, - so it can be deployed on [dotcloud](https://www.dotcloud.com/). +Notice that the protocol is INCOMPATIBLE with shadowsocks. -Notice that the protocol is INCOMPATIBLE with the origin shadowsocks. +## Heroku -usage ------------ +### Usage -Sign up for [dotcloud](https://www.dotcloud.com/). +``` +$ heroku create +Creating still-tor-8707... done, stack is cedar-14 +http://still-tor-8707.herokuapp.com/ | git@heroku.com:still-tor-8707.git +``` -Install [dotcloud CLI](https://docs.dotcloud.com/0.9/firststeps/install/). +Push the code to Heroku. -Put the code somewhere, for example shadowsocks-dotcloud/. Edit `shadowsocks/config.json`, change the following values: +``` +$ git push heroku master +… +-----> Compressing... done, 5.1MB +-----> Launching... done, v3 + http://still-tor-8707.herokuapp.com/ deployed to Heroku - server your server hostname, for example, shadowsocks-YOURUSERNAME.dotcloud.com - local_port local port - password a password used to encrypt transfer - timeout in seconds - method encryption method, null by default, or use "rc4" +To git@heroku.com:still-tor-8707.git + * [new branch] master -> master +``` -Upload the code. You can choose your own app name other than `shadowsocks`. You'll see your hostname at the end. +Set a few configs: - $ dotcloud create shadowsocks - Created application "shadowsocks" using the flavor "sandbox" - ... - $ dotcloud push --application shadowsocks shadowsocks-dotcloud/ - # upload shadowsocks-dotcloud/ ssh://dotcloud@uploader.dotcloud.com:443/shadowsocks - ... - Deployment finished. Your application is available at the following URLs - www: http://shadowsocks-YOURUSERNAME.dotcloud.com/ +``` +$ heroku config:set METHOD=aes-128-cfb KEY=foobar +Setting config vars and restarting still-tor-8707... done, v11 +KEY: foobar +METHOD: aes-128-cfb +``` -Open terminal, cd into shadowsocks, run `node local.js`. +Install project dependencies with `npm install`: -Change proxy settings of your browser into +``` +$ npm install +… +``` - SOCKS5 127.0.0.1:local_port +Then run: +``` +$ node local.js -s still-tor-8707.herokuapp.com -l 1080 -m aes-128-cfb -k foobar -r 80 +server listening at { address: '127.0.0.1', family: 'IPv4', port: 1080 } +``` -troubleshooting ----------------- +Change proxy settings of your browser into: + +``` +SOCKS5 127.0.0.1:1080 +``` + +### Troubleshooting If there is something wrong, you can check the logs by: - $ dotcloud logs www --application shadowsocks +``` +$ heroku logs -t --app still-tor-8707 +``` + +## Supported Ciphers + +- aes-128-cfb +- aes-192-cfb +- aes-256-cfb +- camellia-128-cfb +- camellia-192-cfb +- camellia-256-cfb diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 00000000..05f25037 Binary files /dev/null and b/bun.lockb differ diff --git a/config.json b/config.json new file mode 100644 index 00000000..1c0d084f --- /dev/null +++ b/config.json @@ -0,0 +1,10 @@ +{ + "server": "127.0.0.1", + "local_address": "127.0.0.1", + "scheme": "ws", + "local_port": 1080, + "remote_port": 8080, + "password": "`try*(^^$some^$%^complex>:<>?~password/", + "timeout": 600, + "method": "aes-128-cfb" +} diff --git a/devenv.lock b/devenv.lock new file mode 100644 index 00000000..9c82db41 --- /dev/null +++ b/devenv.lock @@ -0,0 +1,156 @@ +{ + "nodes": { + "devenv": { + "locked": { + "dir": "src/modules", + "lastModified": 1709964002, + "narHash": "sha256-uFYF67deIS6RPMzI7e+E+5QtlMZbrk+wYKz9GiTZOac=", + "owner": "cachix", + "repo": "devenv", + "rev": "be7e8358c1d871021e3d24acc67e2beb41830e10", + "type": "github" + }, + "original": { + "dir": "src/modules", + "owner": "cachix", + "repo": "devenv", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1701680307, + "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "pre-commit-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1703887061, + "narHash": "sha256-gGPa9qWNc6eCXT/+Z5/zMkyYOuRZqeFZBDbopNZQkuY=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "43e1aa1308018f37118e34d3a9cb4f5e75dc11d5", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1710036095, + "narHash": "sha256-joYx0arQtHM/7VhUY5ByP+jlf8XeJkK2fBdri8vK918=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "f592a7ea771bc3c417f7e3026af615d0c6be84ce", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-stable": { + "locked": { + "lastModified": 1704874635, + "narHash": "sha256-YWuCrtsty5vVZvu+7BchAxmcYzTMfolSPP5io8+WYCg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "3dc440faeee9e889fe2d1b4d25ad0f430d449356", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-23.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "pre-commit-hooks": { + "inputs": { + "flake-compat": "flake-compat", + "flake-utils": "flake-utils", + "gitignore": "gitignore", + "nixpkgs": [ + "nixpkgs" + ], + "nixpkgs-stable": "nixpkgs-stable" + }, + "locked": { + "lastModified": 1708018599, + "narHash": "sha256-M+Ng6+SePmA8g06CmUZWi1AjG2tFBX9WCXElBHEKnyM=", + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "rev": "5df5a70ad7575f6601d91f0efec95dd9bc619431", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "type": "github" + } + }, + "root": { + "inputs": { + "devenv": "devenv", + "nixpkgs": "nixpkgs", + "pre-commit-hooks": "pre-commit-hooks" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/devenv.nix b/devenv.nix new file mode 100644 index 00000000..916bd4ef --- /dev/null +++ b/devenv.nix @@ -0,0 +1,16 @@ +{ pkgs, ... }: + +{ + languages.javascript = { + enable = true; + bun = { + enable = true; + install.enable = true; + }; + }; + devcontainer.enable = true; + difftastic.enable = true; + pre-commit.hooks = { + prettier.enable = true; + }; +} diff --git a/devenv.yaml b/devenv.yaml new file mode 100644 index 00000000..c7cb5ced --- /dev/null +++ b/devenv.yaml @@ -0,0 +1,3 @@ +inputs: + nixpkgs: + url: github:NixOS/nixpkgs/nixpkgs-unstable diff --git a/dotcloud.yml b/dotcloud.yml deleted file mode 100644 index f97a8aa1..00000000 --- a/dotcloud.yml +++ /dev/null @@ -1,5 +0,0 @@ -www: - type: nodejs - approot: shadowsocks - config: - node_version: v0.8.x \ No newline at end of file diff --git a/encrypt.js b/encrypt.js new file mode 100644 index 00000000..4218f4f5 --- /dev/null +++ b/encrypt.js @@ -0,0 +1,91 @@ +import crypto from 'crypto'; +import {memoize} from './utils.js'; + +const EVP_BytesToKey = memoize(function (password, key_len, iv_len) { + const m = []; + let i = 0; + let count = 0; + while (count < key_len + iv_len) { + const md5 = crypto.createHash('md5'); + let data = password; + if (i > 0) { + data = Buffer.concat([m[i - 1], password]); + } + md5.update(data); + const d = md5.digest(); + m.push(d); + count += d.length; + i += 1; + } + const ms = Buffer.concat(m); + const key = ms.subarray(0, key_len); + const iv = ms.subarray(key_len, key_len + iv_len); + return [key, iv]; +}); + +const method_supported = { + 'aes-128-cfb': [16, 16], + 'aes-192-cfb': [24, 16], + 'aes-256-cfb': [32, 16], + 'camellia-128-cfb': [16, 16], + 'camellia-192-cfb': [24, 16], + 'camellia-256-cfb': [32, 16], +}; + +export class Encryptor { + constructor(key, method) { + this.key = key; + this.method = method; + this.iv_sent = false; + this.cipher = this.get_cipher( + this.key, + this.method, + 1, + crypto.randomBytes(32), + ); + } + + get_cipher_len(method) { + method = method.toLowerCase(); + return method_supported[method]; + } + + get_cipher(password, method, op, iv) { + method = method.toLowerCase(); + password = Buffer.from(password, 'binary'); + const m = this.get_cipher_len(method); + if (m) { + const [key] = EVP_BytesToKey(password, m[0], m[1]); + if (op === 1) { + this.cipher_iv = iv.subarray(0, m[1]); + } + iv = iv.subarray(0, m[1]); + if (op === 1) { + return crypto.createCipheriv(method, key, iv); + } else { + return crypto.createDecipheriv(method, key, iv); + } + } + } + + encrypt(buf) { + const result = this.cipher.update(buf); + if (this.iv_sent) { + return result; + } else { + this.iv_sent = true; + return Buffer.concat([this.cipher_iv, result]); + } + } + + decrypt(buf) { + if (!this.decipher) { + const decipher_iv_len = this.get_cipher_len(this.method)[1]; + const decipher_iv = buf.subarray(0, decipher_iv_len); + this.decipher = this.get_cipher(this.key, this.method, 0, decipher_iv); + return this.decipher.update(buf.subarray(decipher_iv_len)); + } else { + return this.decipher.update(buf); + } + } +} diff --git a/local.js b/local.js new file mode 100644 index 00000000..b23f779a --- /dev/null +++ b/local.js @@ -0,0 +1,293 @@ +import net from 'net'; +import url from 'url'; +import http from 'http'; +import fs from 'fs'; +import WebSocket from 'ws'; +import parseArgs from 'minimist'; +import {HttpsProxyAgent} from 'https-proxy-agent'; +import {Encryptor} from './encrypt.js'; +import {inetNtoa} from './utils.js'; + +const options = { + alias: { + b: 'local_address', + l: 'local_port', + s: 'server', + r: 'remote_port', + k: 'password', + c: 'config_file', + m: 'method', + }, + string: [ + 'local_address', + 'server', + 'password', + 'config_file', + 'method', + 'scheme', + ], + default: { + config_file: './config.json', + }, +}; + +const configFromArgs = parseArgs(process.argv.slice(2), options); +const configContent = fs.readFileSync(configFromArgs.config_file); +const config = JSON.parse(configContent); +for (let k in configFromArgs) { + const v = configFromArgs[k]; + config[k] = v; +} + +const SCHEME = config.scheme; +let SERVER = config.server; +const REMOTE_PORT = config.remote_port; +const LOCAL_ADDRESS = config.local_address; +const PORT = config.local_port; +const KEY = config.password; +let METHOD = config.method; +const timeout = Math.floor(config.timeout * 1000); +const HTTPPROXY = process.env.http_proxy; + +if (HTTPPROXY) { + console.log('http proxy:', HTTPPROXY); +} + +const prepareServer = function (address) { + const serverUrl = url.parse(address); + serverUrl.slashes = true; + if (!serverUrl.protocol) { + serverUrl.protocol = SCHEME; + } + if (!serverUrl.hostname) { + serverUrl.hostname = address; + serverUrl.pathname = '/'; + } + if (!serverUrl.port) { + serverUrl.port = REMOTE_PORT; + } + return url.format(serverUrl); +}; + +if (SERVER instanceof Array) { + SERVER = SERVER.map((s) => prepareServer(s)); +} else { + SERVER = prepareServer(SERVER); +} + +const getServer = function () { + if (SERVER instanceof Array) { + return SERVER[Math.floor(Math.random() * SERVER.length)]; + } else { + return SERVER; + } +}; + +var server = net.createServer(function (connection) { + console.log('local connected'); + server.getConnections(function (err, count) { + console.log('concurrent connections:', count); + }); + const encryptor = new Encryptor(KEY, METHOD); + let stage = 0; + let cachedPieces = []; + let ws = null; + let remoteAddr = null; + let remotePort = null; + let addrToSend = ''; + const aServer = getServer(); + connection.on('data', function (data) { + if (stage === 5) { + // pipe sockets + data = encryptor.encrypt(data); + if (ws.readyState === WebSocket.OPEN) { + ws.send(data, {binary: true}); + } + return; + } + if (stage === 0) { + connection.write(Buffer.from([5, 0])); + stage = 1; + return; + } + if (stage === 1) { + // +----+-----+-------+------+----------+----------+ + // |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | + // +----+-----+-------+------+----------+----------+ + // | 1 | 1 | X'00' | 1 | Variable | 2 | + // +----+-----+-------+------+----------+----------+ + + let headerLength = 5; + + if (data.length < headerLength) { + connection.end(); + return; + } + const cmd = data[1]; + const addrtype = data[3]; + if (cmd !== 1) { + console.log('unsupported cmd:', cmd); + const reply = Buffer.from('\u0005\u0007\u0000\u0001', 'binary'); + connection.end(reply); + return; + } + if (![1, 3, 4].includes(addrtype)) { + console.log('unsupported addrtype:', addrtype); + connection.end(); + return; + } + addrToSend = data.subarray(3, 4).toString('binary'); + + // read address and port + if (addrtype === 1) { + // ipv4 + headerLength = 4 + 4 + 2; + if (data.length < headerLength) { + connection.end(); + return; + } + remoteAddr = inetNtoa(4, data.subarray(4, 8)); + addrToSend += data.subarray(4, 10).toString('binary'); + remotePort = data.readUInt16BE(8); + } else if (addrtype === 4) { + // ipv6 + headerLength = 4 + 16 + 2; + if (data.length < headerLength) { + connection.end(); + return; + } + remoteAddr = inetNtoa(6, data.subarray(4, 20)); + addrToSend += data.subarray(4, 22).toString('binary'); + remotePort = data.readUInt16BE(20); + } else { + const addrLen = data[4]; + headerLength = 5 + addrLen + 2; + if (data.length < headerLength) { + connection.end(); + return; + } + remoteAddr = data.subarray(5, 5 + addrLen).toString('binary'); + addrToSend += data.subarray(4, 5 + addrLen + 2).toString('binary'); + remotePort = data.readUInt16BE(5 + addrLen); + } + let buf = Buffer.alloc(10); + buf.write('\u0005\u0000\u0000\u0001', 0, 4, 'binary'); + buf.write('\u0000\u0000\u0000\u0000', 4, 4, 'binary'); + buf.writeUInt16BE(remotePort, 8); + connection.write(buf); + // connect to remote server + // ws = new WebSocket aServer, protocol: "binary" + + if (HTTPPROXY) { + // WebSocket endpoint for the proxy to connect to + const endpoint = aServer; + const parsed = url.parse(endpoint); + //console.log('attempting to connect to WebSocket %j', endpoint); + + // create an instance of the `HttpsProxyAgent` class with the proxy server information + const opts = url.parse(HTTPPROXY); + + // IMPORTANT! Set the `secureEndpoint` option to `false` when connecting + // over "ws://", but `true` when connecting over "wss://" + opts.secureEndpoint = parsed.protocol + ? parsed.protocol == 'wss:' + : false; + + const agent = new HttpsProxyAgent(opts); + + ws = new WebSocket(aServer, { + protocol: 'binary', + agent, + }); + } else { + ws = new WebSocket(aServer, { + protocol: 'binary', + }); + } + + ws.on('open', function () { + console.log(`connecting ${remoteAddr} via ${aServer}`); + const data = Buffer.concat([ + Buffer.from(addrToSend, 'binary'), + ...cachedPieces, + ]); + cachedPieces = null; + ws.send(encryptor.encrypt(data), { + binary: true, + }); + stage = 5; + }); + + ws.on('message', function (data, flags) { + connection.write(encryptor.decrypt(data)); + }); + + ws.on('close', function () { + console.log('remote disconnected'); + connection.destroy(); + }); + + ws.on('error', function (e) { + console.log(`remote ${remoteAddr}:${remotePort} error: ${e}`); + connection.destroy(); + server.getConnections(function (err, count) { + console.log('concurrent connections:', count); + }); + }); + + if (data.length > headerLength) { + let buf = Buffer.alloc(data.length - headerLength); + data.copy(buf, 0, headerLength); + cachedPieces.push(buf); + } + stage = 4; + } else if (stage === 4) { + // remote server not connected + // cache received buffers + // make sure no data is lost + cachedPieces.push(data); + } + }); + + connection.on('end', function () { + console.log('local disconnected'); + if (ws) { + ws.terminate(); + } + server.getConnections(function (err, count) { + console.log('concurrent connections:', count); + }); + }); + + connection.on('error', function (e) { + console.log(`local error: ${e}`); + if (ws) { + ws.terminate(); + } + server.getConnections(function (err, count) { + console.log('concurrent connections:', count); + }); + }); + + connection.setTimeout(timeout, function () { + console.log('local timeout'); + connection.destroy(); + if (ws) { + ws.terminate(); + } + }); +}); + +server.listen(PORT, LOCAL_ADDRESS, function () { + const address = server.address(); + console.log('server listening at', address); +}); + +server.on('error', function (e) { + if (e.code === 'EADDRINUSE') { + console.log('address in use, aborting'); + } + process.exit(1); +}); + +export default server; diff --git a/package.json b/package.json new file mode 100644 index 00000000..571ea84e --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "name": "shadowsocks-heroku", + "version": "0.9.10", + "type": "module", + "dependencies": { + "bufferutil": "^4.0.1", + "https-proxy-agent": "^7.0.4", + "minimist": "^1.2.0", + "ws": "^8.16.0" + }, + "scripts": { + "start": "node server.js -b 0.0.0.0", + "format": "prettier --write '*.{js,json}'" + }, + "devDependencies": { + "prettier": "^3.2.5" + } +} diff --git a/server.js b/server.js new file mode 100644 index 00000000..ede36bec --- /dev/null +++ b/server.js @@ -0,0 +1,199 @@ +import net from 'net'; +import fs from 'fs'; +import http from 'http'; +import WebSocket from 'ws'; +import {WebSocketServer} from 'ws'; +import parseArgs from 'minimist'; +import {Encryptor} from './encrypt.js'; +import {inetNtoa} from './utils.js'; + +const options = { + alias: { + b: 'local_address', + r: 'remote_port', + k: 'password', + c: 'config_file', + m: 'method', + }, + string: ['local_address', 'password', 'method', 'config_file'], + default: { + config_file: './config.json', + }, +}; + +const configFromArgs = parseArgs(process.argv.slice(2), options); +const configFile = configFromArgs.config_file; +const configContent = fs.readFileSync(configFile); +const config = JSON.parse(configContent); + +if (process.env.PORT) { + config['remote_port'] = +process.env.PORT; +} +if (process.env.KEY) { + config['password'] = process.env.KEY; +} +if (process.env.METHOD) { + config['method'] = process.env.METHOD; +} + +for (let k in configFromArgs) { + const v = configFromArgs[k]; + config[k] = v; +} + +const timeout = Math.floor(config.timeout * 1000); +const LOCAL_ADDRESS = config.local_address; +const PORT = config.remote_port; +const KEY = config.password; +let METHOD = config.method; +const highWaterMark = +process.env.HIGH_WATER_MARK || 64 * 1024; + +const server = http.createServer(function (req, res) { + res.writeHead(200, {'Content-Type': 'text/plain'}); + res.end('asdf.'); +}); + +const wss = new WebSocketServer({ + server, + autoPong: true, + allowSynchronousEvents: true, + perMessageDeflate: false, +}); + +wss.on('connection', function (ws) { + console.log('server connected'); + console.log('concurrent connections:', wss.clients.size); + const encryptor = new Encryptor(KEY, METHOD); + let stage = 0; + let remote = null; + let cachedPieces = []; + let remoteAddr = null; + let remotePort = null; + ws.on('message', function (data, flags) { + data = encryptor.decrypt(data); + if (stage === 5) { + remote.write(data); + } + if (stage === 0) { + let headerLength = 2; + if (data.length < headerLength) { + ws.close(); + return; + } + const addrtype = data[0]; + if (![1, 3, 4].includes(addrtype)) { + console.warn(`unsupported addrtype: ${addrtype}`); + ws.close(); + return; + } + // read address and port + if (addrtype === 1) { + // ipv4 + headerLength = 1 + 4 + 2; + if (data.length < headerLength) { + ws.close(); + return; + } + remoteAddr = inetNtoa(4, data.subarray(1, 5)); + remotePort = data.readUInt16BE(5); + } else if (addrtype === 4) { + // ipv6 + headerLength = 1 + 16 + 2; + if (data.length < headerLength) { + ws.close(); + return; + } + remoteAddr = inetNtoa(6, data.subarray(1, 17)); + remotePort = data.readUInt16BE(17); + } else { + let addrLen = data[1]; + headerLength = 2 + addrLen + 2; + if (data.length < headerLength) { + ws.close(); + return; + } + remoteAddr = data.subarray(2, 2 + addrLen).toString('binary'); + remotePort = data.readUInt16BE(2 + addrLen); + } + + // connect to remote server + remote = net.connect(remotePort, remoteAddr, function () { + console.log('connecting', remoteAddr); + remote.write(Buffer.concat(cachedPieces)); + cachedPieces = null; + stage = 5; + }); + remote.on('data', function (data) { + if (ws.readyState === WebSocket.OPEN) { + data = encryptor.encrypt(data); + ws.send(data, {binary: true}, (err) => { + if (err) return; + if (ws.bufferedAmount < highWaterMark && remote.isPaused()) + remote.resume(); + }); + if (ws.bufferedAmount >= highWaterMark && !remote.isPaused()) + remote.pause(); + } + }); + + remote.on('end', function () { + ws.close(); + console.log('remote disconnected'); + }); + + remote.on('error', function (e) { + ws.terminate(); + console.log(`remote: ${e}`); + }); + + remote.setTimeout(timeout, function () { + console.log('remote timeout'); + remote.destroy(); + ws.close(); + }); + + if (data.length > headerLength) { + // make sure no data is lost + let buf = Buffer.alloc(data.length - headerLength); + data.copy(buf, 0, headerLength); + cachedPieces.push(buf); + } + stage = 4; + } else if (stage === 4) { + // remote server not connected + // cache received buffers + // make sure no data is lost + cachedPieces.push(data); + } + }); + + ws.on('close', function () { + console.log('server disconnected'); + console.log('concurrent connections:', wss.clients.size); + if (remote) { + remote.destroy(); + } + }); + + ws.on('error', function (e) { + console.warn(`server: ${e}`); + console.log('concurrent connections:', wss.clients.size); + if (remote) { + remote.destroy(); + } + }); +}); + +server.listen(PORT, LOCAL_ADDRESS, function () { + const address = server.address(); + console.log('server listening at', address); +}); + +server.on('error', function (e) { + if (e.code === 'EADDRINUSE') { + console.log('address in use, aborting'); + } + process.exit(1); +}); + +export default server; diff --git a/shadowsocks/Cakefile b/shadowsocks/Cakefile deleted file mode 100644 index 48d8a5c1..00000000 --- a/shadowsocks/Cakefile +++ /dev/null @@ -1,35 +0,0 @@ -{print} = require 'util' -{spawn} = require 'child_process' - -build = (callback) -> - os = require 'os' - if os.platform() == 'win32' - coffeeCmd = 'coffee.cmd' - else - coffeeCmd = 'coffee' - coffee = spawn coffeeCmd, ['-c', '-o', '.', 'src'] - coffee.stderr.on 'data', (data) -> - process.stderr.write data.toString() - coffee.stdout.on 'data', (data) -> - print data.toString() - coffee.on 'exit', (code) -> - console.log 'build completed' - callback?() if code is 0 - process.exit code - -test = (callback) -> - os = require 'os' - coffee = spawn 'node', ['test.js'] - coffee.stderr.on 'data', (data) -> - process.stderr.write data.toString() - coffee.stdout.on 'data', (data) -> - print data.toString() - coffee.on 'exit', (code) -> - callback?() if code is 0 - process.exit code - -task 'build', 'Build ./ from src/', -> - build() - -task 'test', 'Run unit test', -> - test() diff --git a/shadowsocks/Procfile b/shadowsocks/Procfile deleted file mode 100644 index 489b2700..00000000 --- a/shadowsocks/Procfile +++ /dev/null @@ -1 +0,0 @@ -web: node server.js diff --git a/shadowsocks/args.js b/shadowsocks/args.js deleted file mode 100644 index 50b5173b..00000000 --- a/shadowsocks/args.js +++ /dev/null @@ -1,32 +0,0 @@ -// Generated by CoffeeScript 1.4.0 -(function() { - - exports.parseArgs = function() { - var defination, lastKey, nextIsValue, oneArg, result, _, _ref; - defination = { - '-l': 'local_port', - '-s': 'server', - '-k': 'password', - '-c': 'config_file', - '-m': 'method' - }; - result = {}; - nextIsValue = false; - lastKey = null; - _ref = process.argv; - for (_ in _ref) { - oneArg = _ref[_]; - if (nextIsValue) { - result[lastKey] = oneArg; - nextIsValue = false; - } else if (oneArg in defination) { - lastKey = defination[oneArg]; - nextIsValue = true; - } - } - return result; - }; - - exports.version = "shadowsocks-dotcloud v0.9.6"; - -}).call(this); diff --git a/shadowsocks/config.json b/shadowsocks/config.json deleted file mode 100644 index 363bec9b..00000000 --- a/shadowsocks/config.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "server":["projectname-username.dotcloud.com"], - "local_port":1080, - "password":"`try*(^^$some^$%^complex>:<>?~password/", - "timeout":600, - "method":null -} diff --git a/shadowsocks/encrypt.js b/shadowsocks/encrypt.js deleted file mode 100644 index e680b956..00000000 --- a/shadowsocks/encrypt.js +++ /dev/null @@ -1,101 +0,0 @@ -// Generated by CoffeeScript 1.4.0 -(function() { - var Encryptor, cachedTables, crypto, encrypt, getTable, int32Max, merge_sort, util; - - crypto = require("crypto"); - - util = require("util"); - - merge_sort = require("./merge_sort").merge_sort; - - int32Max = Math.pow(2, 32); - - cachedTables = {}; - - getTable = function(key) { - var ah, al, decrypt_table, hash, i, md5sum, result, table; - if (cachedTables[key]) { - return cachedTables[key]; - } - util.log("calculating ciphers"); - table = new Array(256); - decrypt_table = new Array(256); - md5sum = crypto.createHash("md5"); - md5sum.update(key); - hash = new Buffer(md5sum.digest(), "binary"); - al = hash.readUInt32LE(0); - ah = hash.readUInt32LE(4); - i = 0; - while (i < 256) { - table[i] = i; - i++; - } - i = 1; - while (i < 1024) { - table = merge_sort(table, function(x, y) { - return ((ah % (x + i)) * int32Max + al) % (x + i) - ((ah % (y + i)) * int32Max + al) % (y + i); - }); - i++; - } - i = 0; - while (i < 256) { - decrypt_table[table[i]] = i; - ++i; - } - result = [table, decrypt_table]; - cachedTables[key] = result; - return result; - }; - - encrypt = function(table, buf) { - var i; - i = 0; - while (i < buf.length) { - buf[i] = table[buf[i]]; - i++; - } - return buf; - }; - - Encryptor = (function() { - - function Encryptor(key, method) { - var _ref; - this.method = method; - if (this.method != null) { - this.cipher = crypto.createCipher(this.method, key); - this.decipher = crypto.createDecipher(this.method, key); - } else { - _ref = getTable(key), this.encryptTable = _ref[0], this.decryptTable = _ref[1]; - } - } - - Encryptor.prototype.encrypt = function(buf) { - var result; - if (this.method != null) { - result = new Buffer(this.cipher.update(buf.toString('binary')), 'binary'); - return result; - } else { - return encrypt(this.encryptTable, buf); - } - }; - - Encryptor.prototype.decrypt = function(buf) { - var result; - if (this.method != null) { - result = new Buffer(this.decipher.update(buf.toString('binary')), 'binary'); - return result; - } else { - return encrypt(this.decryptTable, buf); - } - }; - - return Encryptor; - - })(); - - exports.Encryptor = Encryptor; - - exports.getTable = getTable; - -}).call(this); diff --git a/shadowsocks/local.js b/shadowsocks/local.js deleted file mode 100644 index ea85c649..00000000 --- a/shadowsocks/local.js +++ /dev/null @@ -1,265 +0,0 @@ -// Generated by CoffeeScript 1.4.0 -(function() { - var Encryptor, KEY, METHOD, PORT, REMOTE_PORT, SERVER, args, config, configContent, configFromArgs, fs, getServer, http, inetAton, inetNtoa, k, net, path, server, timeout, util, v; - - net = require("net"); - - http = require("http"); - - fs = require("fs"); - - path = require("path"); - - util = require('util'); - - args = require('./args'); - - Encryptor = require("./encrypt").Encryptor; - - console.log(args.version); - - inetNtoa = function(buf) { - return buf[0] + "." + buf[1] + "." + buf[2] + "." + buf[3]; - }; - - inetAton = function(ipStr) { - var buf, i, parts; - parts = ipStr.split("."); - if (parts.length !== 4) { - return null; - } else { - buf = new Buffer(4); - i = 0; - while (i < 4) { - buf[i] = +parts[i]; - i++; - } - return buf; - } - }; - - configContent = fs.readFileSync(path.resolve(__dirname, "config.json")); - - config = JSON.parse(configContent); - - configFromArgs = args.parseArgs(); - - for (k in configFromArgs) { - v = configFromArgs[k]; - config[k] = v; - } - - SERVER = config.server; - - REMOTE_PORT = config.remote_port || 80; - - PORT = config.local_port; - - KEY = config.password; - - METHOD = config.method; - - timeout = Math.floor(config.timeout * 1000); - - getServer = function() { - if (SERVER instanceof Array) { - return SERVER[Math.floor(Math.random() * SERVER.length)]; - } else { - return SERVER; - } - }; - - server = net.createServer(function(connection) { - var aServer, addrLen, addrToSend, cachedPieces, encryptor, headerLength, remote, remoteAddr, remotePort, req, stage; - util.log("local connected"); - util.log("concurrent connections: " + server.connections); - encryptor = new Encryptor(KEY, METHOD); - stage = 0; - headerLength = 0; - remote = null; - req = null; - cachedPieces = []; - addrLen = 0; - remoteAddr = null; - remotePort = null; - addrToSend = ""; - aServer = getServer(); - connection.on("data", function(data) { - var addrtype, buf, cmd, reply, tempBuf; - if (stage === 5) { - data = encryptor.encrypt(data); - if (!remote.write(data)) { - connection.pause(); - } - return; - } - if (stage === 0) { - tempBuf = new Buffer(2); - tempBuf.write("\u0005\u0000", 0); - connection.write(tempBuf); - stage = 1; - return; - } - if (stage === 1) { - try { - cmd = data[1]; - addrtype = data[3]; - if (cmd !== 1) { - util.log("unsupported cmd: " + cmd); - reply = new Buffer("\u0005\u0007\u0000\u0001", "binary"); - connection.end(reply); - return; - } - if (addrtype === 3) { - addrLen = data[4]; - } else if (addrtype !== 1) { - util.log("unsupported addrtype: " + addrtype); - connection.end(); - return; - } - addrToSend = data.slice(3, 4).toString("binary"); - if (addrtype === 1) { - remoteAddr = inetNtoa(data.slice(4, 8)); - addrToSend += data.slice(4, 10).toString("binary"); - remotePort = data.readUInt16BE(8); - headerLength = 10; - } else { - remoteAddr = data.slice(5, 5 + addrLen).toString("binary"); - addrToSend += data.slice(4, 5 + addrLen + 2).toString("binary"); - remotePort = data.readUInt16BE(5 + addrLen); - headerLength = 5 + addrLen + 2; - } - buf = new Buffer(10); - buf.write("\u0005\u0000\u0000\u0001", 0, 4, "binary"); - buf.write("\u0000\u0000\u0000\u0000", 4, 4, "binary"); - buf.writeInt16BE(remotePort, 8); - connection.write(buf); - console.log(REMOTE_PORT); - req = http.request({ - host: aServer, - port: REMOTE_PORT, - headers: { - 'Connection': 'Upgrade', - 'Upgrade': 'websocket' - } - }); - req.setNoDelay(true); - req.end(); - req.setTimeout(timeout, function() { - req.abort(); - return connection.end(); - }); - req.on('error', function(e) { - console.warn("req " + e); - req.abort(); - return connection.end(); - }); - req.on('upgrade', function(res, conn, upgradeHead) { - var addrToSendBuf, i, piece; - remote = conn; - util.log("remote got upgrade"); - util.log("connecting " + remoteAddr + " via " + aServer); - addrToSendBuf = new Buffer(addrToSend, "binary"); - addrToSendBuf = encryptor.encrypt(addrToSendBuf); - remote.write(addrToSendBuf); - i = 0; - while (i < cachedPieces.length) { - piece = cachedPieces[i]; - piece = encryptor.encrypt(piece); - remote.write(piece); - i++; - } - cachedPieces = null; - stage = 5; - remote.on("data", function(data) { - data = encryptor.decrypt(data); - if (!connection.write(data)) { - return remote.pause(); - } - }); - remote.on("end", function() { - util.log("remote disconnected"); - connection.end(); - return util.log("concurrent connections: " + server.connections); - }); - remote.on("error", function(e) { - util.log("remote " + remoteAddr + ":" + remotePort + " error: " + e); - if (stage === 4) { - connection.destroy(); - return; - } - connection.end(); - return util.log("concurrent connections: " + server.connections); - }); - remote.on("drain", function() { - return connection.resume(); - }); - return remote.setTimeout(timeout, function() { - connection.end(); - return remote.destroy(); - }); - }); - if (data.length > headerLength) { - buf = new Buffer(data.length - headerLength); - data.copy(buf, 0, headerLength); - cachedPieces.push(buf); - buf = null; - } - return stage = 4; - } catch (e) { - util.log(e); - connection.destroy(); - if (remote) { - return remote.destroy(); - } - } - } else { - if (stage === 4) { - return cachedPieces.push(data); - } - } - }); - connection.on("end", function() { - if (remote) { - remote.destroy(); - } - return util.log("concurrent connections: " + server.connections); - }); - connection.on("error", function(e) { - util.log("local error: " + e); - if (req) { - req.abort(); - } - if (remote) { - remote.destroy(); - } - return util.log("concurrent connections: " + server.connections); - }); - connection.on("drain", function() { - if (remote && stage === 5) { - return remote.resume(); - } - }); - return connection.setTimeout(timeout, function() { - if (req) { - req.abort(); - } - if (remote) { - remote.destroy(); - } - return connection.destroy(); - }); - }); - - server.listen(PORT, function() { - return util.log("server listening at port " + PORT); - }); - - server.on("error", function(e) { - if (e.code === "EADDRINUSE") { - util.log("Address in use, aborting"); - } - return process.exit(1); - }); - -}).call(this); diff --git a/shadowsocks/merge_sort.js b/shadowsocks/merge_sort.js deleted file mode 100644 index ed5188bb..00000000 --- a/shadowsocks/merge_sort.js +++ /dev/null @@ -1,35 +0,0 @@ -// Generated by CoffeeScript 1.4.0 -(function() { - var merge, merge_sort; - - merge = function(left, right, comparison) { - var result; - result = new Array(); - while ((left.length > 0) && (right.length > 0)) { - if (comparison(left[0], right[0]) <= 0) { - result.push(left.shift()); - } else { - result.push(right.shift()); - } - } - while (left.length > 0) { - result.push(left.shift()); - } - while (right.length > 0) { - result.push(right.shift()); - } - return result; - }; - - merge_sort = function(array, comparison) { - var middle; - if (array.length < 2) { - return array; - } - middle = Math.ceil(array.length / 2); - return merge(merge_sort(array.slice(0, middle), comparison), merge_sort(array.slice(middle), comparison), comparison); - }; - - exports.merge_sort = merge_sort; - -}).call(this); diff --git a/shadowsocks/package.json b/shadowsocks/package.json deleted file mode 100644 index bc4a4eed..00000000 --- a/shadowsocks/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "shadowsocks-dotcloud", - "version": "0.9.6", - "dependencies": { - }, - "engines": { - "node": "0.8.x", - "npm": "1.1.x" - } -} \ No newline at end of file diff --git a/shadowsocks/server.js b/shadowsocks/server.js deleted file mode 100644 index 7ef77df8..00000000 --- a/shadowsocks/server.js +++ /dev/null @@ -1,204 +0,0 @@ -// Generated by CoffeeScript 1.4.0 -(function() { - var Encryptor, KEY, METHOD, PORT, args, config, configContent, configFile, configFromArgs, fs, http, inetAton, inetNtoa, k, net, path, portPassword, server, timeout, v; - - net = require("net"); - - fs = require("fs"); - - path = require("path"); - - http = require("http"); - - args = require("./args"); - - Encryptor = require("./encrypt").Encryptor; - - console.log(args.version); - - inetNtoa = function(buf) { - return buf[0] + "." + buf[1] + "." + buf[2] + "." + buf[3]; - }; - - inetAton = function(ipStr) { - var buf, i, parts; - parts = ipStr.split("."); - if (parts.length !== 4) { - return null; - } else { - buf = new Buffer(4); - i = 0; - while (i < 4) { - buf[i] = +parts[i]; - i++; - } - return buf; - } - }; - - configFromArgs = args.parseArgs(); - - configFile = configFromArgs.config_file || path.resolve(__dirname, "config.json"); - - configContent = fs.readFileSync(configFile); - - config = JSON.parse(configContent); - - for (k in configFromArgs) { - v = configFromArgs[k]; - config[k] = v; - } - - timeout = Math.floor(config.timeout * 1000); - - portPassword = config.port_password; - - PORT = 8080; - - KEY = config.password; - - METHOD = config.method; - - server = http.createServer(function(req, res) { - res.writeHead(200, { - 'Content-Type': 'text/plain' - }); - return res.end('Good Day!'); - }); - - server.on('upgrade', function(req, connection, head) { - var addrLen, cachedPieces, encryptor, headerLength, remote, remoteAddr, remotePort, stage; - connection.write('HTTP/1.1 101 Web Socket Protocol Handshake\r\n' + 'Upgrade: WebSocket\r\n' + 'Connection: Upgrade\r\n' + '\r\n'); - console.log("server connected"); - console.log("concurrent connections: " + server.connections); - encryptor = new Encryptor(KEY, METHOD); - stage = 0; - headerLength = 0; - remote = null; - cachedPieces = []; - addrLen = 0; - remoteAddr = null; - remotePort = null; - connection.on("data", function(data) { - var addrtype, buf; - data = encryptor.decrypt(data); - if (stage === 5) { - if (!remote.write(data)) { - connection.pause(); - } - return; - } - if (stage === 0) { - try { - addrtype = data[0]; - if (addrtype === 3) { - addrLen = data[1]; - } else if (addrtype !== 1) { - console.warn("unsupported addrtype: " + addrtype); - connection.end(); - return; - } - if (addrtype === 1) { - remoteAddr = inetNtoa(data.slice(1, 5)); - remotePort = data.readUInt16BE(5); - headerLength = 7; - } else { - remoteAddr = data.slice(2, 2 + addrLen).toString("binary"); - remotePort = data.readUInt16BE(2 + addrLen); - headerLength = 2 + addrLen + 2; - } - console.log(remoteAddr); - remote = net.connect(remotePort, remoteAddr, function() { - var i, piece; - console.log("connecting " + remoteAddr); - i = 0; - while (i < cachedPieces.length) { - piece = cachedPieces[i]; - remote.write(piece); - i++; - } - cachedPieces = null; - return stage = 5; - }); - remote.on("data", function(data) { - data = encryptor.encrypt(data); - if (!connection.write(data)) { - return remote.pause(); - } - }); - remote.on("end", function() { - console.log("remote disconnected"); - console.log("concurrent connections: " + server.connections); - return connection.end(); - }); - remote.on("error", function(e) { - console.log("remote : " + e); - connection.destroy(); - return console.log("concurrent connections: " + server.connections); - }); - remote.on("drain", function() { - return connection.resume(); - }); - remote.setTimeout(timeout, function() { - connection.end(); - return remote.destroy(); - }); - if (data.length > headerLength) { - buf = new Buffer(data.length - headerLength); - data.copy(buf, 0, headerLength); - cachedPieces.push(buf); - buf = null; - } - return stage = 4; - } catch (e) { - console.warn(e); - connection.destroy(); - if (remote) { - return remote.destroy(); - } - } - } else { - if (stage === 4) { - return cachedPieces.push(data); - } - } - }); - connection.on("end", function() { - console.log("server disconnected"); - if (remote) { - remote.destroy(); - } - return console.log("concurrent connections: " + server.connections); - }); - connection.on("error", function(e) { - console.warn("server : " + e); - if (remote) { - remote.destroy(); - } - return console.log("concurrent connections: " + server.connections); - }); - connection.on("drain", function() { - if (remote) { - return remote.resume(); - } - }); - return connection.setTimeout(timeout, function() { - if (remote) { - remote.destroy(); - } - return connection.destroy(); - }); - }); - - server.listen(PORT, function() { - return console.log("server listening at port " + PORT); - }); - - server.on("error", function(e) { - if (e.code === "EADDRINUSE") { - console.warn("Address in use, aborting"); - } - return process.exit(1); - }); - -}).call(this); diff --git a/shadowsocks/src/args.coffee b/shadowsocks/src/args.coffee deleted file mode 100644 index 77ccb1e6..00000000 --- a/shadowsocks/src/args.coffee +++ /dev/null @@ -1,23 +0,0 @@ - -exports.parseArgs = -> - defination = - '-l': 'local_port' - '-s': 'server' - '-k': 'password', - '-c': 'config_file', - '-m': 'method' - - - result = {} - nextIsValue = false - lastKey = null - for _, oneArg of process.argv - if nextIsValue - result[lastKey] = oneArg - nextIsValue = false - else if oneArg of defination - lastKey = defination[oneArg] - nextIsValue = true - result - -exports.version = "shadowsocks-dotcloud v0.9.6" \ No newline at end of file diff --git a/shadowsocks/src/encrypt.coffee b/shadowsocks/src/encrypt.coffee deleted file mode 100644 index 342679a9..00000000 --- a/shadowsocks/src/encrypt.coffee +++ /dev/null @@ -1,91 +0,0 @@ -# Copyright (c) 2012 clowwindy -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -crypto = require("crypto") -util = require("util") -merge_sort = require("./merge_sort").merge_sort -int32Max = Math.pow(2, 32) - -cachedTables = {} # password: [encryptTable, decryptTable] - -getTable = (key) -> - if cachedTables[key] - return cachedTables[key] - util.log "calculating ciphers" - table = new Array(256) - decrypt_table = new Array(256) - md5sum = crypto.createHash("md5") - md5sum.update key - hash = new Buffer(md5sum.digest(), "binary") - al = hash.readUInt32LE(0) - ah = hash.readUInt32LE(4) - i = 0 - - while i < 256 - table[i] = i - i++ - i = 1 - - while i < 1024 - table = merge_sort(table, (x, y) -> - ((ah % (x + i)) * int32Max + al) % (x + i) - ((ah % (y + i)) * int32Max + al) % (y + i) - ) - i++ - i = 0 - while i < 256 - decrypt_table[table[i]] = i - ++i - result = [table, decrypt_table] - cachedTables[key] = result - result - -encrypt = (table, buf) -> - i = 0 - - while i < buf.length - buf[i] = table[buf[i]] - i++ - buf - - -class Encryptor - constructor: (key, @method) -> - if @method? - @cipher = crypto.createCipher @method, key - @decipher = crypto.createDecipher @method, key - else - [@encryptTable, @decryptTable] = getTable(key) - - encrypt: (buf) -> - if @method? - result = new Buffer(@cipher.update(buf.toString('binary')), 'binary') - result - else - encrypt @encryptTable, buf - - decrypt: (buf) -> - if @method? - result = new Buffer(@decipher.update(buf.toString('binary')), 'binary') - result - else - encrypt @decryptTable, buf - -exports.Encryptor = Encryptor -exports.getTable = getTable diff --git a/shadowsocks/src/local.coffee b/shadowsocks/src/local.coffee deleted file mode 100644 index d8a92dd9..00000000 --- a/shadowsocks/src/local.coffee +++ /dev/null @@ -1,230 +0,0 @@ -# Copyright (c) 2012 clowwindy -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - - -net = require("net") -http = require("http") -fs = require("fs") -path = require("path") -util = require('util') -args = require('./args') -Encryptor = require("./encrypt").Encryptor - -console.log(args.version) - -inetNtoa = (buf) -> - buf[0] + "." + buf[1] + "." + buf[2] + "." + buf[3] -inetAton = (ipStr) -> - parts = ipStr.split(".") - unless parts.length is 4 - null - else - buf = new Buffer(4) - i = 0 - - while i < 4 - buf[i] = +parts[i] - i++ - buf - -configContent = fs.readFileSync(path.resolve(__dirname, "config.json")) -config = JSON.parse(configContent) -configFromArgs = args.parseArgs() -for k, v of configFromArgs - config[k] = v -SERVER = config.server -REMOTE_PORT = config.remote_port || 80 -PORT = config.local_port -KEY = config.password -METHOD = config.method -timeout = Math.floor(config.timeout * 1000) - -getServer = -> - if SERVER instanceof Array - SERVER[Math.floor(Math.random() * SERVER.length)] - else - SERVER - - -server = net.createServer((connection) -> - util.log "local connected" - util.log "concurrent connections: " + server.connections - encryptor = new Encryptor(KEY, METHOD) - stage = 0 - headerLength = 0 - remote = null - req = null - cachedPieces = [] - addrLen = 0 - remoteAddr = null - remotePort = null - addrToSend = "" - aServer = getServer() - connection.on "data", (data) -> - if stage is 5 - # pipe sockets - data = encryptor.encrypt data - connection.pause() unless remote.write(data) - return - if stage is 0 - tempBuf = new Buffer(2) - tempBuf.write "\u0005\u0000", 0 - connection.write tempBuf - stage = 1 - return - if stage is 1 - try - # +----+-----+-------+------+----------+----------+ - # |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | - # +----+-----+-------+------+----------+----------+ - # | 1 | 1 | X'00' | 1 | Variable | 2 | - # +----+-----+-------+------+----------+----------+ - - #cmd and addrtype - cmd = data[1] - addrtype = data[3] - unless cmd is 1 - util.log "unsupported cmd: " + cmd - reply = new Buffer("\u0005\u0007\u0000\u0001", "binary") - connection.end reply - return - if addrtype is 3 - addrLen = data[4] - else unless addrtype is 1 - util.log "unsupported addrtype: " + addrtype - connection.end() - return - addrToSend = data.slice(3, 4).toString("binary") - # read address and port - if addrtype is 1 - remoteAddr = inetNtoa(data.slice(4, 8)) - addrToSend += data.slice(4, 10).toString("binary") - remotePort = data.readUInt16BE(8) - headerLength = 10 - else - remoteAddr = data.slice(5, 5 + addrLen).toString("binary") - addrToSend += data.slice(4, 5 + addrLen + 2).toString("binary") - remotePort = data.readUInt16BE(5 + addrLen) - headerLength = 5 + addrLen + 2 - buf = new Buffer(10) - buf.write "\u0005\u0000\u0000\u0001", 0, 4, "binary" - buf.write "\u0000\u0000\u0000\u0000", 4, 4, "binary" - buf.writeInt16BE remotePort, 8 - connection.write buf - # connect remote server - console.log REMOTE_PORT - req = http.request( - host: aServer, - port: REMOTE_PORT, - headers: - 'Connection': 'Upgrade', - 'Upgrade': 'websocket' - ) - req.setNoDelay true - req.end() - req.setTimeout timeout, -> - req.abort() - connection.end() - req.on 'error', (e)-> - console.warn "req #{e}" - req.abort() - connection.end() - req.on 'upgrade', (res, conn, upgradeHead) -> - remote = conn - util.log "remote got upgrade" - util.log "connecting #{remoteAddr} via #{aServer}" - addrToSendBuf = new Buffer(addrToSend, "binary") - addrToSendBuf = encryptor.encrypt addrToSendBuf - remote.write addrToSendBuf - i = 0 - - while i < cachedPieces.length - piece = cachedPieces[i] - piece = encryptor.encrypt piece - remote.write piece - i++ - cachedPieces = null # save memory - stage = 5 - - remote.on "data", (data) -> - data = encryptor.decrypt data - remote.pause() unless connection.write(data) - - remote.on "end", -> - util.log "remote disconnected" - connection.end() - util.log "concurrent connections: " + server.connections - - remote.on "error", (e)-> - util.log "remote #{remoteAddr}:#{remotePort} error: #{e}" - if stage is 4 - connection.destroy() - return - connection.end() - util.log "concurrent connections: " + server.connections - - remote.on "drain", -> - connection.resume() - - remote.setTimeout timeout, -> - connection.end() - remote.destroy() - - if data.length > headerLength - buf = new Buffer(data.length - headerLength) - data.copy buf, 0, headerLength - cachedPieces.push buf - buf = null - stage = 4 - catch e - # may encounter index out of range - util.log e - connection.destroy() - remote.destroy() if remote - else cachedPieces.push data if stage is 4 - # remote server not connected - # cache received buffers - # make sure no data is lost - - connection.on "end", -> - remote.destroy() if remote - util.log "concurrent connections: " + server.connections - - connection.on "error", (e)-> - util.log "local error: #{e}" - req.abort() if req - remote.destroy() if remote - util.log "concurrent connections: " + server.connections - - connection.on "drain", -> - # calling resume() when remote not is connected will crash node.js - remote.resume() if remote and stage is 5 - - connection.setTimeout timeout, -> - req.abort() if req - remote.destroy() if remote - connection.destroy() -) -server.listen PORT, -> - util.log "server listening at port " + PORT - -server.on "error", (e) -> - util.log "Address in use, aborting" if e.code is "EADDRINUSE" - process.exit 1 diff --git a/shadowsocks/src/merge_sort.coffee b/shadowsocks/src/merge_sort.coffee deleted file mode 100644 index 1733e984..00000000 --- a/shadowsocks/src/merge_sort.coffee +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright (c) 2012 clowwindy -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -merge = (left, right, comparison) -> - result = new Array() - while (left.length > 0) and (right.length > 0) - if comparison(left[0], right[0]) <= 0 - result.push left.shift() - else - result.push right.shift() - result.push left.shift() while left.length > 0 - result.push right.shift() while right.length > 0 - result -merge_sort = (array, comparison) -> - return array if array.length < 2 - middle = Math.ceil(array.length / 2) - merge merge_sort(array.slice(0, middle), comparison), merge_sort(array.slice(middle), comparison), comparison -exports.merge_sort = merge_sort diff --git a/shadowsocks/src/server.coffee b/shadowsocks/src/server.coffee deleted file mode 100644 index 8e2b5ab9..00000000 --- a/shadowsocks/src/server.coffee +++ /dev/null @@ -1,174 +0,0 @@ -# Copyright (c) 2012 clowwindy -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -net = require("net") -fs = require("fs") -path = require("path") -http = require("http") -args = require("./args") -Encryptor = require("./encrypt").Encryptor - -console.log(args.version) - -inetNtoa = (buf) -> - buf[0] + "." + buf[1] + "." + buf[2] + "." + buf[3] -inetAton = (ipStr) -> - parts = ipStr.split(".") - unless parts.length is 4 - null - else - buf = new Buffer(4) - i = 0 - - while i < 4 - buf[i] = +parts[i] - i++ - buf - -configFromArgs = args.parseArgs() -configFile = configFromArgs.config_file or path.resolve(__dirname, "config.json") -configContent = fs.readFileSync(configFile) -config = JSON.parse(configContent) -for k, v of configFromArgs - config[k] = v -timeout = Math.floor(config.timeout * 1000) -portPassword = config.port_password -PORT = 8080 -KEY = config.password -METHOD = config.method -#SERVER = config.server - - -server = http.createServer (req, res) -> - res.writeHead 200, 'Content-Type':'text/plain' - res.end 'Good Day!' - -server.on 'upgrade', (req, connection, head) -> - connection.write 'HTTP/1.1 101 Web Socket Protocol Handshake\r\n' + - 'Upgrade: WebSocket\r\n' + - 'Connection: Upgrade\r\n' + - '\r\n' - console.log "server connected" - console.log "concurrent connections: " + server.connections - encryptor = new Encryptor(KEY, METHOD) - stage = 0 - headerLength = 0 - remote = null - cachedPieces = [] - addrLen = 0 - remoteAddr = null - remotePort = null - connection.on "data", (data) -> - data = encryptor.decrypt data - if stage is 5 - connection.pause() unless remote.write(data) - return - if stage is 0 - try - addrtype = data[0] - if addrtype is 3 - addrLen = data[1] - else unless addrtype is 1 - console.warn "unsupported addrtype: " + addrtype - connection.end() - return - # read address and port - if addrtype is 1 - remoteAddr = inetNtoa(data.slice(1, 5)) - remotePort = data.readUInt16BE(5) - headerLength = 7 - else - remoteAddr = data.slice(2, 2 + addrLen).toString("binary") - remotePort = data.readUInt16BE(2 + addrLen) - headerLength = 2 + addrLen + 2 - console.log remoteAddr - # connect remote server - remote = net.connect(remotePort, remoteAddr, -> - console.log "connecting " + remoteAddr - i = 0 - - while i < cachedPieces.length - piece = cachedPieces[i] - remote.write piece - i++ - cachedPieces = null # save memory - stage = 5 - ) - remote.on "data", (data) -> - data = encryptor.encrypt data - remote.pause() unless connection.write(data) - - remote.on "end", -> - console.log "remote disconnected" - console.log "concurrent connections: " + server.connections - connection.end() - - remote.on "error", (e)-> - console.log "remote : #{e}" - connection.destroy() - console.log "concurrent connections: " + server.connections - - remote.on "drain", -> - connection.resume() - - remote.setTimeout timeout, -> - connection.end() - remote.destroy() - - if data.length > headerLength - # make sure no data is lost - buf = new Buffer(data.length - headerLength) - data.copy buf, 0, headerLength - cachedPieces.push buf - buf = null - stage = 4 - catch e - # may encouter index out of range - console.warn e - connection.destroy() - remote.destroy() if remote - else cachedPieces.push data if stage is 4 - # remote server not connected - # cache received buffers - # make sure no data is lost - - connection.on "end", -> - console.log "server disconnected" - remote.destroy() if remote - console.log "concurrent connections: " + server.connections - - connection.on "error", (e)-> - console.warn "server : #{e}" - remote.destroy() if remote - console.log "concurrent connections: " + server.connections - - connection.on "drain", -> - remote.resume() if remote - - connection.setTimeout timeout, -> - remote.destroy() if remote - connection.destroy() - -server.listen PORT, -> - console.log "server listening at port " + PORT - -server.on "error", (e) -> - console.warn "Address in use, aborting" if e.code is "EADDRINUSE" - process.exit 1 diff --git a/shadowsocks/src/test.coffee b/shadowsocks/src/test.coffee deleted file mode 100644 index d629a597..00000000 --- a/shadowsocks/src/test.coffee +++ /dev/null @@ -1,76 +0,0 @@ -# test encryption -encrypt = require("./encrypt") -target = [ - [ 60, 53, 84, 138, 217, 94, 88, 23, 39, 242, 219, 35, 12, 157, 165, 181, 255, 143, 83, 247, 162, 16, 31, 209, 190, 171, 115, 65, 38, 41, 21, 245, 236, 46, 121, 62, 166, 233, 44, 154, 153, 145, 230, 49, 128, 216, 173, 29, 241, 119, 64, 229, 194, 103, 131, 110, 26, 197, 218, 59, 204, 56, 27, 34, 141, 221, 149, 239, 192, 195, 24, 155, 170, 183, 11, 254, 213, 37, 137, 226, 75, 203, 55, 19, 72, 248, 22, 129, 33, 175, 178, 10, 198, 71, 77, 36, 113, 167, 48, 2, 117, 140, 142, 66, 199, 232, 243, 32, 123, 54, 51, 82, 57, 177, 87, 251, 150, 196, 133, 5, 253, 130, 8, 184, 14, 152, 231, 3, 186, 159, 76, 89, 228, 205, 156, 96, 163, 146, 18, 91, 132, 85, 80, 109, 172, 176, 105, 13, 50, 235, 127, 0, 189, 95, 98, 136, 250, 200, 108, 179, 211, 214, 106, 168, 78, 79, 74, 210, 30, 73, 201, 151, 208, 114, 101, 174, 92, 52, 120, 240, 15, 169, 220, 182, 81, 224, 43, 185, 40, 99, 180, 17, 212, 158, 42, 90, 9, 191, 45, 6, 25, 4, 222, 67, 126, 1, 116, 124, 206, 69, 61, 7, 68, 97, 202, 63, 244, 20, 28, 58, 93, 134, 104, 144, 227, 147, 102, 118, 135, 148, 47, 238, 86, 112, 122, 70, 107, 215, 100, 139, 223, 225, 164, 237, 111, 125, 207, 160, 187, 246, 234, 161, 188, 193, 249, 252 ], - [ 151, 205, 99, 127, 201, 119, 199, 211, 122, 196, 91, 74, 12, 147, 124, 180, 21, 191, 138, 83, 217, 30, 86, 7, 70, 200, 56, 62, 218, 47, 168, 22, 107, 88, 63, 11, 95, 77, 28, 8, 188, 29, 194, 186, 38, 198, 33, 230, 98, 43, 148, 110, 177, 1, 109, 82, 61, 112, 219, 59, 0, 210, 35, 215, 50, 27, 103, 203, 212, 209, 235, 93, 84, 169, 166, 80, 130, 94, 164, 165, 142, 184, 111, 18, 2, 141, 232, 114, 6, 131, 195, 139, 176, 220, 5, 153, 135, 213, 154, 189, 238, 174, 226, 53, 222, 146, 162, 236, 158, 143, 55, 244, 233, 96, 173, 26, 206, 100, 227, 49, 178, 34, 234, 108, 207, 245, 204, 150, 44, 87, 121, 54, 140, 118, 221, 228, 155, 78, 3, 239, 101, 64, 102, 17, 223, 41, 137, 225, 229, 66, 116, 171, 125, 40, 39, 71, 134, 13, 193, 129, 247, 251, 20, 136, 242, 14, 36, 97, 163, 181, 72, 25, 144, 46, 175, 89, 145, 113, 90, 159, 190, 15, 183, 73, 123, 187, 128, 248, 252, 152, 24, 197, 68, 253, 52, 69, 117, 57, 92, 104, 157, 170, 214, 81, 60, 133, 208, 246, 172, 23, 167, 160, 192, 76, 161, 237, 45, 4, 58, 10, 182, 65, 202, 240, 185, 241, 79, 224, 132, 51, 42, 126, 105, 37, 250, 149, 32, 243, 231, 67, 179, 48, 9, 106, 216, 31, 249, 19, 85, 254, 156, 115, 255, 120, 75, 16 ] -] -tables = encrypt.getTable("foobar!") -console.log JSON.stringify(tables) -i = 0 - -while i < 256 - console.assert tables[0][i] is target[0][i] - console.assert tables[1][i] is target[1][i] - i++ - -# test proxy - -child_process = require('child_process') -local = child_process.spawn('node', ['local.js']) -server = child_process.spawn('node', ['server.js']) - -curlRunning = false - -local.on 'exit', (code)-> - server.kill() - if !curlRunning - process.exit code - -server.on 'exit', (code)-> - local.kill() - if !curlRunning - process.exit code - -localReady = false -serverReady = false -curlRunning = false - -runCurl = -> - curlRunning = true - curl = child_process.spawn 'curl', ['-v', 'http://www.example.com/', '-L', '--socks5-hostname', '127.0.0.1:1080'] - curl.on 'exit', (code)-> - local.kill() - server.kill() - if code is 0 - console.log 'Test passed' - process.exit 0 - else - console.error 'Test failed' - process.exit code - - curl.stdout.on 'data', (data) -> - console.log data.toString() - - curl.stderr.on 'data', (data) -> - console.warn data.toString() - -local.stderr.on 'data', (data) -> - console.warn data.toString() - -server.stderr.on 'data', (data) -> - console.warn data.toString() - -local.stdout.on 'data', (data) -> - console.log data.toString() - if data.toString().indexOf('listening at port') >= 0 - localReady = true - if localReady and serverReady and not curlRunning - runCurl() - -server.stdout.on 'data', (data) -> - console.log data.toString() - if data.toString().indexOf('listening at port') >= 0 - serverReady = true - if localReady and serverReady and not curlRunning - runCurl() - diff --git a/shadowsocks/supervisord.conf b/shadowsocks/supervisord.conf deleted file mode 100644 index 5995eeee..00000000 --- a/shadowsocks/supervisord.conf +++ /dev/null @@ -1,3 +0,0 @@ -[program:node] -command = node server.js -directory = /home/dotcloud/current \ No newline at end of file diff --git a/shadowsocks/test.js b/shadowsocks/test.js deleted file mode 100644 index 8a934d38..00000000 --- a/shadowsocks/test.js +++ /dev/null @@ -1,100 +0,0 @@ -// Generated by CoffeeScript 1.4.0 -(function() { - var child_process, curlRunning, encrypt, i, local, localReady, runCurl, server, serverReady, tables, target; - - encrypt = require("./encrypt"); - - target = [[60, 53, 84, 138, 217, 94, 88, 23, 39, 242, 219, 35, 12, 157, 165, 181, 255, 143, 83, 247, 162, 16, 31, 209, 190, 171, 115, 65, 38, 41, 21, 245, 236, 46, 121, 62, 166, 233, 44, 154, 153, 145, 230, 49, 128, 216, 173, 29, 241, 119, 64, 229, 194, 103, 131, 110, 26, 197, 218, 59, 204, 56, 27, 34, 141, 221, 149, 239, 192, 195, 24, 155, 170, 183, 11, 254, 213, 37, 137, 226, 75, 203, 55, 19, 72, 248, 22, 129, 33, 175, 178, 10, 198, 71, 77, 36, 113, 167, 48, 2, 117, 140, 142, 66, 199, 232, 243, 32, 123, 54, 51, 82, 57, 177, 87, 251, 150, 196, 133, 5, 253, 130, 8, 184, 14, 152, 231, 3, 186, 159, 76, 89, 228, 205, 156, 96, 163, 146, 18, 91, 132, 85, 80, 109, 172, 176, 105, 13, 50, 235, 127, 0, 189, 95, 98, 136, 250, 200, 108, 179, 211, 214, 106, 168, 78, 79, 74, 210, 30, 73, 201, 151, 208, 114, 101, 174, 92, 52, 120, 240, 15, 169, 220, 182, 81, 224, 43, 185, 40, 99, 180, 17, 212, 158, 42, 90, 9, 191, 45, 6, 25, 4, 222, 67, 126, 1, 116, 124, 206, 69, 61, 7, 68, 97, 202, 63, 244, 20, 28, 58, 93, 134, 104, 144, 227, 147, 102, 118, 135, 148, 47, 238, 86, 112, 122, 70, 107, 215, 100, 139, 223, 225, 164, 237, 111, 125, 207, 160, 187, 246, 234, 161, 188, 193, 249, 252], [151, 205, 99, 127, 201, 119, 199, 211, 122, 196, 91, 74, 12, 147, 124, 180, 21, 191, 138, 83, 217, 30, 86, 7, 70, 200, 56, 62, 218, 47, 168, 22, 107, 88, 63, 11, 95, 77, 28, 8, 188, 29, 194, 186, 38, 198, 33, 230, 98, 43, 148, 110, 177, 1, 109, 82, 61, 112, 219, 59, 0, 210, 35, 215, 50, 27, 103, 203, 212, 209, 235, 93, 84, 169, 166, 80, 130, 94, 164, 165, 142, 184, 111, 18, 2, 141, 232, 114, 6, 131, 195, 139, 176, 220, 5, 153, 135, 213, 154, 189, 238, 174, 226, 53, 222, 146, 162, 236, 158, 143, 55, 244, 233, 96, 173, 26, 206, 100, 227, 49, 178, 34, 234, 108, 207, 245, 204, 150, 44, 87, 121, 54, 140, 118, 221, 228, 155, 78, 3, 239, 101, 64, 102, 17, 223, 41, 137, 225, 229, 66, 116, 171, 125, 40, 39, 71, 134, 13, 193, 129, 247, 251, 20, 136, 242, 14, 36, 97, 163, 181, 72, 25, 144, 46, 175, 89, 145, 113, 90, 159, 190, 15, 183, 73, 123, 187, 128, 248, 252, 152, 24, 197, 68, 253, 52, 69, 117, 57, 92, 104, 157, 170, 214, 81, 60, 133, 208, 246, 172, 23, 167, 160, 192, 76, 161, 237, 45, 4, 58, 10, 182, 65, 202, 240, 185, 241, 79, 224, 132, 51, 42, 126, 105, 37, 250, 149, 32, 243, 231, 67, 179, 48, 9, 106, 216, 31, 249, 19, 85, 254, 156, 115, 255, 120, 75, 16]]; - - tables = encrypt.getTable("foobar!"); - - console.log(JSON.stringify(tables)); - - i = 0; - - while (i < 256) { - console.assert(tables[0][i] === target[0][i]); - console.assert(tables[1][i] === target[1][i]); - i++; - } - - child_process = require('child_process'); - - local = child_process.spawn('node', ['local.js']); - - server = child_process.spawn('node', ['server.js']); - - curlRunning = false; - - local.on('exit', function(code) { - server.kill(); - if (!curlRunning) { - return process.exit(code); - } - }); - - server.on('exit', function(code) { - local.kill(); - if (!curlRunning) { - return process.exit(code); - } - }); - - localReady = false; - - serverReady = false; - - curlRunning = false; - - runCurl = function() { - var curl; - curlRunning = true; - curl = child_process.spawn('curl', ['-v', 'http://www.example.com/', '-L', '--socks5-hostname', '127.0.0.1:1080']); - curl.on('exit', function(code) { - local.kill(); - server.kill(); - if (code === 0) { - console.log('Test passed'); - return process.exit(0); - } else { - console.error('Test failed'); - return process.exit(code); - } - }); - curl.stdout.on('data', function(data) { - return console.log(data.toString()); - }); - return curl.stderr.on('data', function(data) { - return console.warn(data.toString()); - }); - }; - - local.stderr.on('data', function(data) { - return console.warn(data.toString()); - }); - - server.stderr.on('data', function(data) { - return console.warn(data.toString()); - }); - - local.stdout.on('data', function(data) { - console.log(data.toString()); - if (data.toString().indexOf('listening at port') >= 0) { - localReady = true; - if (localReady && serverReady && !curlRunning) { - return runCurl(); - } - } - }); - - server.stdout.on('data', function(data) { - console.log(data.toString()); - if (data.toString().indexOf('listening at port') >= 0) { - serverReady = true; - if (localReady && serverReady && !curlRunning) { - return runCurl(); - } - } - }); - -}).call(this); diff --git a/shadowsocks/test/config.json b/shadowsocks/test/config.json deleted file mode 100644 index 4870dbe4..00000000 --- a/shadowsocks/test/config.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "server":"localhost", - "local_port":1080, - "remote_port":8080, - "password":"`try*(^^$some^$%^complex>:<>?~password/", - "timeout":600, - "method":null -} diff --git a/shadowsocks/test_http.js b/shadowsocks/test_http.js deleted file mode 100644 index 6e4c002c..00000000 --- a/shadowsocks/test_http.js +++ /dev/null @@ -1,49 +0,0 @@ -var http = require('http'); - -// Create an HTTP server -var srv = http.createServer(function (req, res) { - res.writeHead(200, {'Content-Type':'text/plain'}); - console.log('okay'); - res.end('okay'); -}); -srv.on('upgrade', function (req, socket, head) { - socket.write('HTTP/1.1 101 Web Socket Protocol Handshake\r\n' + - 'Upgrade: WebSocket\r\n' + - 'Connection: Upgrade\r\n' + - '\r\n'); - socket.on('data', function (data) { - console.log('srv on data'); - console.log(data); - socket.write('hello'); - }); - -// socket.pipe(socket); // echo back -}); - -// now that server is running -srv.listen(1337, '127.0.0.1', function () { - - // make a request - var options = { - port:1337, - host:'127.0.0.1', - headers:{ - 'Connection':'Upgrade', - 'Upgrade':'websocket' - } - }; - - var req = http.request(options); - req.end(); - - req.on('upgrade', function (res, socket, upgradeHead) { - console.log('got upgraded!'); - socket.write('test'); - socket.on('data', function (data) { - console.log('req on data'); - console.log(data); - }); -// socket.end(); -// process.exit(0); - }); -}); \ No newline at end of file diff --git a/shadowsocks/web.js b/shadowsocks/web.js deleted file mode 100644 index ae74d48d..00000000 --- a/shadowsocks/web.js +++ /dev/null @@ -1,22 +0,0 @@ -net = require("net"); - -PORT = process.env.PORT || 5000; -server = net.createServer(function (connection) { - connection.write('HTTP/1.1 200 OK\r\nContent-Type:text/plain\r\n\r\nd\r\nHello world!\n\r\n'); - setTimeout(function () { - connection.write('6\r\nOver!\n\r\n0\r\n'); - connection.end(); - }, 5000); - - connection.on("error", function (e) { - }); -}); -server.listen(PORT, function () { - return console.log("server listening at port " + PORT); -}); - -server.on("error", function (e) { - if (e.code === "EADDRINUSE") { - return console.warn("Address in use, aborting"); - } -}); diff --git a/test.js b/test.js new file mode 100644 index 00000000..84fed0ca --- /dev/null +++ b/test.js @@ -0,0 +1,36 @@ +import child_process from 'child_process'; +import local from './local.js'; +import server from './server.js'; + +async function runCurl() { + const curl = child_process.spawn('curl', [ + '-v', + 'https://example.com', + '-L', + '--socks5', + '127.0.0.1:1080', + ]); + curl.on('exit', function (code) { + if (code === 0) { + console.log('Test passed'); + process.exit(0); + } else { + console.error('Test failed'); + process.exit(code); + } + }); + + curl.stdout.pipe(process.stdout); + curl.stderr.pipe(process.stderr); + + await new Promise((r) => { + curl.on('close', r); + }); +} + +while (true) { + if (local.listening && server.listening) { + await runCurl(); + } + await new Promise((r) => setTimeout(r, 100)); +} diff --git a/utils.js b/utils.js new file mode 100644 index 00000000..24defdf5 --- /dev/null +++ b/utils.js @@ -0,0 +1,24 @@ +export function inetNtoa(family, buf) { + if (family === 4) return buf[0] + '.' + buf[1] + '.' + buf[2] + '.' + buf[3]; + else if (family === 6) { + let addr = []; + for (let i = 0; i < 8; i++) { + addr.push(buf.readUInt16BE(i * 2, i * 2 + 2).toString(16)); + } + return addr.join(':'); + } +} + +export function memoize(func) { + const cache = {}; + + return function (...args) { + const key = args.join(''); + if (cache[key]) return cache[key]; + + const result = func.apply(this, args); + cache[key] = result; + + return result; + }; +} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 00000000..6dd95dc5 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,58 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 +# bun ./bun.lockb --hash: E3146EB5D1A7B6E0-9e360d3d2def5fac-22F8EAE3B40BE372-8d61ebf8f267f773 + + +agent-base@^7.0.2: + version "7.1.0" + resolved "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz" + integrity sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg== + dependencies: + debug "^4.3.4" + +bufferutil@^4.0.1: + version "4.0.8" + resolved "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.8.tgz" + integrity sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw== + dependencies: + node-gyp-build "^4.3.0" + +debug@4, debug@^4.3.4: + version "4.3.4" + resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +https-proxy-agent@^7.0.4: + version "7.0.4" + resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz" + integrity sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg== + dependencies: + agent-base "^7.0.2" + debug "4" + +minimist@^1.2.0: + version "1.2.8" + resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +node-gyp-build@^4.3.0: + version "4.8.0" + resolved "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.0.tgz" + integrity sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og== + +prettier@^3.2.5: + version "3.2.5" + resolved "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz" + integrity sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A== + +ws@^8.16.0: + version "8.16.0" + resolved "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz" + integrity sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==