diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 917fc8e934..3d93e70737 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,6 +1,5 @@ * @Jigsaw-Code/outline-dev -/src/tun2socks/ @Jigsaw-Code/outline-networking-owners /src/cordova/plugin/ @Jigsaw-Code/outline-networking-owners /third_party/ @Jigsaw-Code/outline-networking-owners /tools/ @Jigsaw-Code/outline-networking-owners diff --git a/.github/workflows/build_and_test_debug.yml b/.github/workflows/build_and_test_debug.yml index f2c98cc8c0..144cf98b5e 100644 --- a/.github/workflows/build_and_test_debug.yml +++ b/.github/workflows/build_and_test_debug.yml @@ -13,7 +13,6 @@ on: branches: - master -# TODO: run go tests jobs: web_test: name: Web Test @@ -64,11 +63,6 @@ jobs: - name: Install NPM Dependencies run: npm ci - - name: Install Go - uses: actions/setup-go@v4 - with: - go-version-file: '${{ github.workspace }}/go.mod' - - name: Build Linux Client run: npm run action electron/build linux @@ -93,11 +87,6 @@ jobs: - name: Install NPM Dependencies run: npm ci - - name: Install Go - uses: actions/setup-go@v4 - with: - go-version-file: '${{ github.workspace }}/go.mod' - - name: Build Windows Client run: npm run action electron/build windows @@ -122,14 +111,6 @@ jobs: - name: Install NPM Dependencies run: npm ci - - name: Install Go - uses: actions/setup-go@v4 - with: - go-version-file: '${{ github.workspace }}/go.mod' - - - name: Build Tun2Socks (required for Test OutlineAppleLib) - run: npm run action tun2socks/build macos - - name: Test OutlineAppleLib run: npm run action cordova/test macos @@ -163,14 +144,6 @@ jobs: - name: Install NPM Dependencies run: npm ci - - name: Install Go - uses: actions/setup-go@v4 - with: - go-version-file: '${{ github.workspace }}/go.mod' - - - name: Build Tun2Socks (required for Test OutlineAppleLib) - run: npm run action tun2socks/build ios - - name: Test OutlineAppleLib run: npm run action cordova/test ios @@ -204,14 +177,6 @@ jobs: - name: Install NPM Dependencies run: npm ci - - name: Install Go - uses: actions/setup-go@v4 - with: - go-version-file: '${{ github.workspace }}/go.mod' - - - name: Build Tun2Socks (required for Test OutlineAppleLib) - run: npm run action tun2socks/build maccatalyst - - name: Test OutlineAppleLib run: npm run action cordova/test maccatalyst @@ -242,11 +207,6 @@ jobs: - name: Install NPM Dependencies run: npm ci - - name: Install Go - uses: actions/setup-go@v4 - with: - go-version-file: '${{ github.workspace }}/go.mod' - - name: Install Java uses: actions/setup-java@v1.4.3 with: diff --git a/.gitignore b/.gitignore index db795c141d..5416836a03 100644 --- a/.gitignore +++ b/.gitignore @@ -1,28 +1,28 @@ .idea -.vs/ .vscode *.DS_Store -*.env -*.pdb -*.sw? -/node_modules -/build /output +/build +info.txt +/node_modules /platforms /plugins -/third_party/Potatso/Pods /www -coverage -info.txt -keystore.p12 +/third_party/Potatso/Pods +xcuserdata/ +.vs/ obj/ -Outline.apk -Outline.apks packages/ -toc.pb +*.pdb tools/OutlineService/OutlineService/bin/* -tools/smartdnsblock/bin/* !tools/OutlineService/OutlineService/bin/*.exe +*.sw? +tools/smartdnsblock/bin/* !tools/smartdnsblock/bin/*.exe +keystore.p12 +Outline.apk +Outline.apks universal.apk -xcuserdata/ +toc.pb +coverage +*.env \ No newline at end of file diff --git a/Makefile b/Makefile deleted file mode 100644 index 9a9bf3a245..0000000000 --- a/Makefile +++ /dev/null @@ -1,76 +0,0 @@ -BUILDDIR=$(CURDIR)/output/build -GOBIN=$(CURDIR)/output/bin - -GOMOBILE=$(GOBIN)/gomobile -# Add GOBIN to $PATH so `gomobile` can find `gobind`. -GOBIND=env PATH="$(GOBIN):$(PATH)" "$(GOMOBILE)" bind -IMPORT_HOST=github.com -IMPORT_PATH=$(IMPORT_HOST)/Jigsaw-Code/outline-client - -.PHONY: android apple linux windows - -all: android apple linux windows - -ROOT_PKG=src/tun2socks -# Don't strip Android debug symbols so we can upload them to crash reporting tools. -ANDROID_BUILD_CMD=$(GOBIND) -a -ldflags '-w' -target=android -androidapi 19 -tags android -work - -android: $(BUILDDIR)/android/tun2socks.aar - -$(BUILDDIR)/android/tun2socks.aar: $(GOMOBILE) - mkdir -p "$(BUILDDIR)/android" - $(ANDROID_BUILD_CMD) -o "$@" $(IMPORT_PATH)/$(ROOT_PKG)/outline/tun2socks $(IMPORT_PATH)/$(ROOT_PKG)/outline/shadowsocks - -# TODO(fortuna): -s strips symbols and is obsolete. Why are we using it? -$(BUILDDIR)/ios/Tun2socks.xcframework: $(GOMOBILE) - # -iosversion should match what outline-client supports. - $(GOBIND) -iosversion=11.0 -target=ios,iossimulator -o $@ -ldflags '-s -w' -bundleid org.outline.tun2socks $(IMPORT_PATH)/$(ROOT_PKG)/outline/tun2socks $(IMPORT_PATH)/$(ROOT_PKG)/outline/shadowsocks - -$(BUILDDIR)/macos/Tun2socks.xcframework: $(GOMOBILE) - # MACOSX_DEPLOYMENT_TARGET and -iosversion should match what outline-client supports. - export MACOSX_DEPLOYMENT_TARGET=10.14; $(GOBIND) -iosversion=13.1 -target=macos,maccatalyst -o $@ -ldflags '-s -w' -bundleid org.outline.tun2socks $(IMPORT_PATH)/$(ROOT_PKG)/outline/tun2socks $(IMPORT_PATH)/$(ROOT_PKG)/outline/shadowsocks - -apple: $(BUILDDIR)/apple/Tun2socks.xcframework - -$(BUILDDIR)/apple/Tun2socks.xcframework: $(BUILDDIR)/ios/Tun2socks.xcframework $(BUILDDIR)/macos/Tun2socks.xcframework - find $^ -name "Tun2socks.framework" -type d | xargs -I {} echo " -framework {} " | \ - xargs xcrun xcodebuild -create-xcframework -output "$@" - -XGO=$(GOBIN)/xgo -TUN2SOCKS_VERSION=v1.16.11 -XGO_LDFLAGS='-s -w -X main.version=$(TUN2SOCKS_VERSION)' -ELECTRON_PKG=$(ROOT_PKG)/outline/electron - -# TODO: build directly when on linux -LINUX_BUILDDIR=$(BUILDDIR)/linux - -linux: $(LINUX_BUILDDIR)/tun2socks - -$(LINUX_BUILDDIR)/tun2socks: $(XGO) - mkdir -p "$(LINUX_BUILDDIR)/$(IMPORT_PATH)" - $(XGO) -ldflags $(XGO_LDFLAGS) --targets=linux/amd64 -dest "$(LINUX_BUILDDIR)" -pkg $(ELECTRON_PKG) . - mv "$(LINUX_BUILDDIR)/$(IMPORT_PATH)-linux-amd64" "$@" - rm -r "$(LINUX_BUILDDIR)/$(IMPORT_HOST)" - -# TODO: build directly when on windows -WINDOWS_BUILDDIR=$(BUILDDIR)/windows - -windows: $(WINDOWS_BUILDDIR)/tun2socks.exe - -$(WINDOWS_BUILDDIR)/tun2socks.exe: $(XGO) - mkdir -p "$(WINDOWS_BUILDDIR)/$(IMPORT_PATH)" - $(XGO) -ldflags $(XGO_LDFLAGS) --targets=windows/386 -dest "$(WINDOWS_BUILDDIR)" -pkg $(ELECTRON_PKG) . - mv "$(WINDOWS_BUILDDIR)/$(IMPORT_PATH)-windows-386.exe" "$@" - rm -r "$(WINDOWS_BUILDDIR)/$(IMPORT_HOST)" - - -$(GOMOBILE): go.mod - env GOBIN="$(GOBIN)" go install golang.org/x/mobile/cmd/gomobile - env GOBIN="$(GOBIN)" $(GOMOBILE) init - -$(XGO): go.mod - env GOBIN="$(GOBIN)" go install github.com/crazy-max/xgo - -go.mod: tools.go - go mod tidy - touch go.mod diff --git a/README.md b/README.md index 92ab8b7146..398dd5d194 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ To join our Outline Community, [sign up for the IFF Mattermost](https://internet ## Requirements for all builds -All builds require [Node](https://nodejs.org/) 18 (lts/hydrogen), and [Go](https://golang.org/) 1.20 installed in addition to other per-platform requirements. +All builds require [Node](https://nodejs.org/) 18 (lts/hydrogen), in addition to other per-platform requirements. > 💡 NOTE: if you have `nvm` installed, run `nvm use` to switch to the correct node version! diff --git a/go.mod b/go.mod index efbd0781fc..4e74235ce5 100644 --- a/go.mod +++ b/go.mod @@ -1,27 +1,23 @@ -module github.com/Jigsaw-Code/outline-client +module github.com/Jigsaw-Code/outline-apps go 1.20 require ( - github.com/Jigsaw-Code/outline-sdk v0.0.9 + github.com/Jigsaw-Code/outline-sdk v0.0.2 github.com/Jigsaw-Code/outline-sdk/x v0.0.0-20230807220427-893de7fdc6b8 - github.com/crazy-max/xgo v0.30.0 - github.com/eycorsican/go-tun2socks v1.16.11 github.com/stretchr/testify v1.8.4 - golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a - golang.org/x/sys v0.15.0 + golang.org/x/sys v0.11.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect + github.com/eycorsican/go-tun2socks v1.16.11 // indirect github.com/miekg/dns v1.1.54 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/shadowsocks/go-shadowsocks2 v0.1.5 // indirect - github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 // indirect - golang.org/x/crypto v0.16.0 // indirect - golang.org/x/mod v0.14.0 // indirect - golang.org/x/net v0.19.0 // indirect - golang.org/x/sync v0.5.0 // indirect - golang.org/x/tools v0.16.0 // indirect + golang.org/x/crypto v0.9.0 // indirect + golang.org/x/mod v0.10.0 // indirect + golang.org/x/net v0.10.0 // indirect + golang.org/x/tools v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 1ba35cb8e4..56672c45c6 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,7 @@ -github.com/Jigsaw-Code/outline-sdk v0.0.9 h1:FuyrqJ5OBh5y8mpXkSomdGJreGi8bAOWRXRNB2B+Hdc= -github.com/Jigsaw-Code/outline-sdk v0.0.9/go.mod h1:hhlKz0+r9wSDFT8usvN8Zv/BFToCIFAUn1P2Qk8G2CM= +github.com/Jigsaw-Code/outline-sdk v0.0.2 h1:uCuyJMaWj57IYEG/Hdml8YMdk9chU60ZkSxJXBhyGHU= +github.com/Jigsaw-Code/outline-sdk v0.0.2/go.mod h1:hhlKz0+r9wSDFT8usvN8Zv/BFToCIFAUn1P2Qk8G2CM= github.com/Jigsaw-Code/outline-sdk/x v0.0.0-20230807220427-893de7fdc6b8 h1:BxOHmmuppPM8K0DGUsfvajKF4PKfGxv9boNDhmbszFU= github.com/Jigsaw-Code/outline-sdk/x v0.0.0-20230807220427-893de7fdc6b8/go.mod h1:tBqJXpVm+kym+EAUdwNodcFxy872FfjVErfj8Br+gs0= -github.com/crazy-max/xgo v0.30.0 h1:2uunjwLBrVu5LKIS1dIDXz9U5OIX4H5LEsC3P6wFTto= -github.com/crazy-max/xgo v0.30.0/go.mod h1:m/aqfKaN/cYzfw+Pzk7Mk0tkmShg3/rCS4Zdhdugi4o= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/eycorsican/go-tun2socks v1.16.11 h1:+hJDNgisrYaGEqoSxhdikMgMJ4Ilfwm/IZDrWRrbaH8= @@ -20,32 +18,27 @@ github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstv github.com/shadowsocks/go-shadowsocks2 v0.1.5 h1:PDSQv9y2S85Fl7VBeOMF9StzeXZyK1HakRm86CUbr28= github.com/shadowsocks/go-shadowsocks2 v0.1.5/go.mod h1:AGGpIoek4HRno4xzyFiAtLHkOpcoznZEkAccaI/rplM= github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E= -github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 h1:TG/diQgUe0pntT/2D9tmUCz4VNwm9MfrtPr0SU2qSX8= -github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= -golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a h1:sYbmY3FwUWCBTodZL1S3JUuOvaW6kM2o+clDzzDNBWg= -golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a/go.mod h1:Ede7gF0KGoHlj822RtphAHK1jLdrcuRBZg0sF1Q+SPc= -golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20191021144547-ec77196f6094/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= -golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= -golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM= -golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= +golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= +golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/package.json b/package.json index 1a05d103ea..422791fa78 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "action:help": "npm run action list", "action:list": "npm run action list", "action": "node ./src/build/run_action.mjs", - "clean": "rimraf build output node_modules www platforms plugins third_party/jsign/*.jar", + "clean": "rimraf output node_modules www platforms plugins third_party/jsign/*.jar", "format:all": "prettier --write \"**/*.{cjs,mjs,html,js,json,md,ts}\"", "format": "pretty-quick --staged --pattern \"**/*.{cjs,mjs,html,js,json,md,ts}\"", "lint:ts": "eslint --ext ts,mjs src", diff --git a/src/build/run_action.mjs b/src/build/run_action.mjs index 8189833c28..c775cf43c5 100644 --- a/src/build/run_action.mjs +++ b/src/build/run_action.mjs @@ -141,5 +141,5 @@ async function main() { } if (import.meta.url === url.pathToFileURL(process.argv[1]).href) { - await main(...process.argv.slice(2)); + await main(); } diff --git a/src/build/spawn_stream.mjs b/src/build/spawn_stream.mjs index 26c68cd72c..199583daf0 100644 --- a/src/build/spawn_stream.mjs +++ b/src/build/spawn_stream.mjs @@ -23,7 +23,7 @@ export const spawnStream = (command, ...parameters) => const stdout = []; const stderr = []; - console.debug(chalk.gray(`Running [${[command, ...parameters.map(e => `'${e}'`)].join(' ')}]...`)); + console.debug(`Running [${[command, ...parameters.map(e => `'${e}'`)].join(' ')}]`); const childProcess = spawn(command, parameters, {env: process.env}); const forEachMessageLine = (buffer, callback) => { @@ -47,22 +47,14 @@ export const spawnStream = (command, ...parameters) => if (code === 0) { return resolve(stdout.join('')); } - console.error( chalk.red( `ERROR(spawn_stream): ${chalk.underline( [command, ...parameters].join(' ') - )} failed with exit code ${chalk.bold(code)}.}` + )} failed with exit code ${chalk.bold(code)}. Printing stderr:` ) ); - - if (!(stderr.length && stderr.every(line => line))) { - console.error(chalk.bgRedBright('No error output was given... Please fix this so it gives an error output :(')); - } else { - console.error(chalk.bgRedBright('Printing stderr:')); - stderr.forEach(error => console.error(chalk.rgb(128, 64, 64)(error))); - } - - return reject(stderr.join('')); + stderr.forEach(error => console.error(chalk.rgb(128, 64, 64)(error))); + return reject(code); }); }); diff --git a/src/cordova/apple/OutlineAppleLib/Package.swift b/src/cordova/apple/OutlineAppleLib/Package.swift index 2643cd1112..41743129bb 100644 --- a/src/cordova/apple/OutlineAppleLib/Package.swift +++ b/src/cordova/apple/OutlineAppleLib/Package.swift @@ -76,7 +76,8 @@ let package = Package( ), .binaryTarget( name: "Tun2socks", - path: "../../../../output/build/apple/Tun2socks.xcframework" + url: "https://github.com/Jigsaw-Code/outline-go-tun2socks/releases/download/v3.4.0/apple.zip", + checksum: "6c6880fa7d419a5fddc10588edffa0b23b5a44f0f840cf6865372127285bcc42" ), .testTarget( name: "OutlineTunnelTest", diff --git a/src/cordova/build.action.mjs b/src/cordova/build.action.mjs index bab23166ba..2847258f26 100644 --- a/src/cordova/build.action.mjs +++ b/src/cordova/build.action.mjs @@ -34,7 +34,6 @@ export async function main(...parameters) { const {platform, buildMode, verbose} = getBuildParameters(parameters); await runAction('www/build', ...parameters); - await runAction('tun2socks/build', ...parameters); await runAction('cordova/setup', ...parameters); if (verbose) { diff --git a/src/cordova/plugin/android/scripts/copy_third_party.js b/src/cordova/plugin/android/scripts/copy_third_party.js index 9f8e6388eb..b56e487314 100644 --- a/src/cordova/plugin/android/scripts/copy_third_party.js +++ b/src/cordova/plugin/android/scripts/copy_third_party.js @@ -20,7 +20,7 @@ const path = require('node:path'); const ANDROID_LIBS_FOLDER_PATH = path.join('plugins', 'cordova-plugin-outline', 'android', 'libs'); const TUN2SOCKS_ANDROID_FOLDER_PATH = path.join('third_party', 'outline-go-tun2socks', 'android'); -module.exports = async function (context) { +module.exports = async function(context) { console.log('Copying Android third party libraries...'); await fs.mkdir(ANDROID_LIBS_FOLDER_PATH, {recursive: true}); await fs.copyFile( diff --git a/src/cordova/setup.action.mjs b/src/cordova/setup.action.mjs index b12713fbe2..e4c9ca4400 100644 --- a/src/cordova/setup.action.mjs +++ b/src/cordova/setup.action.mjs @@ -40,7 +40,6 @@ export async function main(...parameters) { const {platform, buildMode, verbose, buildNumber, versionName} = getBuildParameters(parameters); await runAction('www/build', ...parameters); - await runAction('tun2socks/build', ...parameters); await rmfr(path.resolve(getRootDir(), 'platforms')); await rmfr(path.resolve(getRootDir(), 'plugins')); diff --git a/src/electron/README.md b/src/electron/README.md index 331658da54..9c59290747 100644 --- a/src/electron/README.md +++ b/src/electron/README.md @@ -2,20 +2,6 @@ Unlike the Android and Apple clients, the Windows and Linux clients use the Electron framework, rather than Cordova. -You will need [Docker](https://www.docker.com/) installed to build the Electron clients. - -> If you can't use Docker, you can use [podman](https://podman.io) as substitute by running the following (for macOS): - -```sh -brew install podman -podman machine init -sudo ln -s $(which podman) /usr/local/bin/docker -sudo /opt/homebrew/Cellar/podman//bin/podman-mac-helper install -podman machine start -``` - -> You may run into the error: `/var/folders//xgo-cache: no such file or directory`. If so, simply create that directory with `mkdir -p /var/folders//xgo-cache` and try again. - To build the Electron clients, run (it will also package an installer executable into `build/dist`): ```sh diff --git a/src/electron/build.action.mjs b/src/electron/build.action.mjs index 5afa64b7ab..c231c87bed 100644 --- a/src/electron/build.action.mjs +++ b/src/electron/build.action.mjs @@ -49,7 +49,6 @@ export async function main(...parameters) { } await runAction('www/build', ...parameters); - await runAction('tun2socks/build', ...parameters); await runAction('electron/build_main', ...parameters); await copydir.sync( diff --git a/src/electron/electron-builder.json b/src/electron/electron-builder.json index 1fb45d7ae0..2a6b014ff8 100644 --- a/src/electron/electron-builder.json +++ b/src/electron/electron-builder.json @@ -3,14 +3,14 @@ "asarUnpack": ["third_party", "tools"], "artifactName": "Outline-Client.${ext}", "directories": { - "output": "output/build/dist" + "output": "build/dist" }, "linux": { "target": { "target": "AppImage", "arch": ["x64"] }, - "files": ["build/icons/png", "output/build/linux", "tools/outline_proxy_controller/dist"], + "files": ["build/icons/png", "third_party/outline-go-tun2socks/linux", "tools/outline_proxy_controller/dist"], "icon": "build/icons/png", "category": "Network" }, @@ -21,7 +21,7 @@ "arch": "ia32" } ], - "files": ["output/build/windows"], + "files": ["third_party/outline-go-tun2socks/win32"], "icon": "build/icons/win/icon.ico", "sign": "src/electron/windows/electron_builder_signing_plugin.cjs", "signingHashAlgorithms": ["sha256"] diff --git a/src/tun2socks/build.action.mjs b/src/tun2socks/build.action.mjs deleted file mode 100644 index 6a1f2694d9..0000000000 --- a/src/tun2socks/build.action.mjs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2023 The Outline Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import url from 'url'; -import {spawnStream} from '../build/spawn_stream.mjs'; -import {getBuildParameters} from '../build/get_build_parameters.mjs'; - -/** - * @description Builds the tun2socks library for the specified platform. - * - * @param {string[]} parameters - */ -export async function main(...parameters) { - const {platform} = getBuildParameters(parameters); - - await spawnStream('make', ['ios', 'macos', 'maccatalyst'].includes(platform) ? 'apple' : platform); -} - -if (import.meta.url === url.pathToFileURL(process.argv[1]).href) { - await main(...process.argv.slice(2)); -} diff --git a/src/tun2socks/outline/client.go b/src/tun2socks/outline/client.go deleted file mode 100644 index 1754f48c7b..0000000000 --- a/src/tun2socks/outline/client.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2023 The Outline Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package outline - -import ( - "github.com/Jigsaw-Code/outline-sdk/transport" -) - -// Client provides a transparent container for [transport.StreamDialer] and [transport.PacketListener] -// that is exportable (as an opaque object) via gobind. -// It's used by the connectivity test and the tun2socks handlers. -type Client struct { - transport.StreamDialer - transport.PacketListener -} diff --git a/src/tun2socks/outline/connectivity/connectivity.go b/src/tun2socks/outline/connectivity/connectivity.go deleted file mode 100644 index 34c5ded2eb..0000000000 --- a/src/tun2socks/outline/connectivity/connectivity.go +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright 2023 The Outline Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package connectivity - -import ( - "context" - "errors" - "net" - "net/http" - "time" - - "github.com/Jigsaw-Code/outline-client/src/tun2socks/outline" - "github.com/Jigsaw-Code/outline-client/src/tun2socks/outline/neterrors" - "github.com/Jigsaw-Code/outline-sdk/transport" -) - -// TODO: make these values configurable by exposing a struct with the connectivity methods. -const ( - tcpTimeout = 10 * time.Second - udpTimeout = 1 * time.Second - udpMaxRetryAttempts = 5 - bufferLength = 512 -) - -// authenticationError is used to signal failed authentication to the Shadowsocks proxy. -type authenticationError struct { - error -} - -// reachabilityError is used to signal an unreachable proxy. -type reachabilityError struct { - error -} - -// CheckConnectivity determines whether the Shadowsocks proxy can relay TCP and UDP traffic under -// the current network. Parallelizes the execution of TCP and UDP checks, selects the appropriate -// error code to return accounting for transient network failures. -// Returns an error if an unexpected error ocurrs. -func CheckConnectivity(client *outline.Client) (neterrors.Error, error) { - // Start asynchronous UDP support check. - udpChan := make(chan error) - go func() { - resolverAddr := &net.UDPAddr{IP: net.ParseIP("1.1.1.1"), Port: 53} - udpChan <- CheckUDPConnectivityWithDNS(client, resolverAddr) - }() - // Check whether the proxy is reachable and that the client is able to authenticate to the proxy - tcpErr := CheckTCPConnectivityWithHTTP(client, "http://example.com") - if tcpErr == nil { - udpErr := <-udpChan - if udpErr == nil { - return neterrors.NoError, nil - } - return neterrors.UDPConnectivity, nil - } - var authErr *authenticationError - var reachabilityErr *reachabilityError - if errors.As(tcpErr, &authErr) { - return neterrors.AuthenticationFailure, nil - } else if errors.As(tcpErr, &reachabilityErr) { - return neterrors.Unreachable, nil - } - // The error is not related to the connectivity checks. - return neterrors.Unexpected, tcpErr -} - -// CheckUDPConnectivityWithDNS determines whether the Shadowsocks proxy represented by `client` and -// the network support UDP traffic by issuing a DNS query though a resolver at `resolverAddr`. -// Returns nil on success or an error on failure. -func CheckUDPConnectivityWithDNS(client transport.PacketListener, resolverAddr net.Addr) error { - conn, err := client.ListenPacket(context.Background()) - if err != nil { - return err - } - defer conn.Close() - buf := make([]byte, bufferLength) - for attempt := 0; attempt < udpMaxRetryAttempts; attempt++ { - conn.SetDeadline(time.Now().Add(udpTimeout)) - _, err := conn.WriteTo(getDNSRequest(), resolverAddr) - if err != nil { - continue - } - n, addr, err := conn.ReadFrom(buf) - if n == 0 && err != nil { - continue - } - if addr.String() != resolverAddr.String() { - continue // Ensure we got a response from the resolver. - } - return nil - } - return errors.New("UDP connectivity check timed out") -} - -// CheckTCPConnectivityWithHTTP determines whether the proxy is reachable over TCP and validates the -// client's authentication credentials by performing an HTTP HEAD request to `targetURL`, which must -// be of the form: http://[host](:[port])(/[path]). Returns nil on success, error if `targetURL` is -// invalid, AuthenticationError or ReachabilityError on connectivity failure. -func CheckTCPConnectivityWithHTTP(dialer transport.StreamDialer, targetURL string) error { - deadline := time.Now().Add(tcpTimeout) - ctx, cancel := context.WithDeadline(context.Background(), deadline) - defer cancel() - req, err := http.NewRequest("HEAD", targetURL, nil) - if err != nil { - return err - } - targetAddr := req.Host - if !hasPort(targetAddr) { - targetAddr = net.JoinHostPort(targetAddr, "80") - } - conn, err := dialer.Dial(ctx, targetAddr) - if err != nil { - return &reachabilityError{err} - } - defer conn.Close() - conn.SetDeadline(deadline) - err = req.Write(conn) - if err != nil { - return &authenticationError{err} - } - n, err := conn.Read(make([]byte, bufferLength)) - if n == 0 && err != nil { - return &authenticationError{err} - } - return nil -} - -func getDNSRequest() []byte { - return []byte{ - 0, 0, // [0-1] query ID - 1, 0, // [2-3] flags; byte[2] = 1 for recursion desired (RD). - 0, 1, // [4-5] QDCOUNT (number of queries) - 0, 0, // [6-7] ANCOUNT (number of answers) - 0, 0, // [8-9] NSCOUNT (number of name server records) - 0, 0, // [10-11] ARCOUNT (number of additional records) - 3, 'c', 'o', 'm', - 0, // null terminator of FQDN (root TLD) - 0, 1, // QTYPE, set to A - 0, 1, // QCLASS, set to 1 = IN (Internet) - } -} - -func hasPort(hostPort string) bool { - _, _, err := net.SplitHostPort(hostPort) - return err == nil -} diff --git a/src/tun2socks/outline/connectivity/connectivity_test.go b/src/tun2socks/outline/connectivity/connectivity_test.go deleted file mode 100644 index 5f4078c4c8..0000000000 --- a/src/tun2socks/outline/connectivity/connectivity_test.go +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright 2023 The Outline Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package connectivity - -import ( - "context" - "errors" - "net" - "reflect" - "testing" - "time" - - "github.com/Jigsaw-Code/outline-sdk/transport" - "github.com/Jigsaw-Code/outline-sdk/transport/shadowsocks" -) - -func TestCheckUDPConnectivityWithDNS_Success(t *testing.T) { - client := &fakeSSClient{} - err := CheckUDPConnectivityWithDNS(client, &net.UDPAddr{}) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } -} - -func TestCheckUDPConnectivityWithDNS_Fail(t *testing.T) { - client := &fakeSSClient{failUDP: true} - err := CheckUDPConnectivityWithDNS(client, &net.UDPAddr{}) - if err == nil { - t.Fail() - } -} - -func TestCheckTCPConnectivityWithHTTP_Success(t *testing.T) { - client := &fakeSSClient{} - err := CheckTCPConnectivityWithHTTP(client, "") - if err != nil { - t.Fail() - } -} - -func TestCheckTCPConnectivityWithHTTP_FailReachability(t *testing.T) { - client := &fakeSSClient{failReachability: true} - err := CheckTCPConnectivityWithHTTP(client, "") - if err == nil { - t.Fail() - } - if _, ok := err.(*reachabilityError); !ok { - t.Fatalf("Expected reachability error, got: %v", reflect.TypeOf(err)) - } -} - -func TestCheckTCPConnectivityWithHTTP_FailAuthentication(t *testing.T) { - client := &fakeSSClient{failAuthentication: true} - err := CheckTCPConnectivityWithHTTP(client, "") - if err == nil { - t.Fail() - } - if _, ok := err.(*authenticationError); !ok { - t.Fatalf("Expected authentication error, got: %v", reflect.TypeOf(err)) - } -} - -// Fake shadowsocks.Client that can be configured to return failing UDP and TCP connections. -type fakeSSClient struct { - failReachability bool - failAuthentication bool - failUDP bool -} - -func (c *fakeSSClient) Dial(_ context.Context, raddr string) (transport.StreamConn, error) { - if c.failReachability { - return nil, &net.OpError{} - } - return &fakeDuplexConn{failRead: c.failAuthentication}, nil -} -func (c *fakeSSClient) ListenPacket(_ context.Context) (net.PacketConn, error) { - conn, err := net.ListenPacket("udp", "") - if err != nil { - return nil, err - } - // The UDP check should fail if any of the failure conditions are true since it is a superset of the others. - failRead := c.failAuthentication || c.failUDP || c.failReachability - return &fakePacketConn{PacketConn: conn, failRead: failRead}, nil -} -func (c *fakeSSClient) SetTCPSaltGenerator(salter shadowsocks.SaltGenerator) { -} - -// Fake PacketConn that fails `ReadFrom` calls when `failRead` is true. -type fakePacketConn struct { - net.PacketConn - addr net.Addr - failRead bool -} - -func (c *fakePacketConn) WriteTo(b []byte, addr net.Addr) (int, error) { - c.addr = addr - return len(b), nil // Write always succeeds -} - -func (c *fakePacketConn) ReadFrom(b []byte) (int, net.Addr, error) { - if c.failRead { - return 0, c.addr, errors.New("Fake read error") - } - return len(b), c.addr, nil -} - -// Fake DuplexConn that fails `Read` calls when `failRead` is true. -type fakeDuplexConn struct { - transport.StreamConn - failRead bool -} - -func (c *fakeDuplexConn) Read(b []byte) (int, error) { - if c.failRead { - return 0, errors.New("Fake read error") - } - return len(b), nil -} - -func (c *fakeDuplexConn) Write(b []byte) (int, error) { - return len(b), nil // Write always succeeds -} - -func (c *fakeDuplexConn) Close() error { return nil } - -func (c *fakeDuplexConn) LocalAddr() net.Addr { return nil } - -func (c *fakeDuplexConn) RemoteAddr() net.Addr { return nil } - -func (c *fakeDuplexConn) SetDeadline(t time.Time) error { return nil } - -func (c *fakeDuplexConn) SetReadDeadline(t time.Time) error { return nil } - -func (c *fakeDuplexConn) SetWriteDeadline(t time.Time) error { return nil } - -func (c *fakeDuplexConn) CloseRead() error { return nil } - -func (c *fakeDuplexConn) CloseWrite() error { return nil } diff --git a/src/tun2socks/outline/electron/main.go b/src/tun2socks/outline/electron/main.go deleted file mode 100644 index 42bdae3d90..0000000000 --- a/src/tun2socks/outline/electron/main.go +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright 2019 The Outline Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package main - -import ( - "flag" - "fmt" - "io" - "os" - "os/signal" - "strings" - "syscall" - "time" - - "github.com/Jigsaw-Code/outline-client/src/tun2socks/outline/internal/utf8" - "github.com/Jigsaw-Code/outline-client/src/tun2socks/outline/neterrors" - "github.com/Jigsaw-Code/outline-client/src/tun2socks/outline/shadowsocks" - "github.com/Jigsaw-Code/outline-client/src/tun2socks/outline/tun2socks" - "github.com/eycorsican/go-tun2socks/common/log" - _ "github.com/eycorsican/go-tun2socks/common/log/simple" // Register a simple logger. - "github.com/eycorsican/go-tun2socks/core" - "github.com/eycorsican/go-tun2socks/proxy/dnsfallback" - "github.com/eycorsican/go-tun2socks/tun" -) - -const ( - mtu = 1500 - udpTimeout = 30 * time.Second - persistTun = true // Linux: persist the TUN interface after the last open file descriptor is closed. -) - -var args struct { - tunAddr *string - tunGw *string - tunMask *string - tunName *string - tunDNS *string - - // Deprecated: Use proxyConfig instead. - proxyHost *string - proxyPort *int - proxyPassword *string - proxyCipher *string - proxyPrefix *string - - proxyConfig *string - - logLevel *string - checkConnectivity *bool - dnsFallback *bool - version *bool -} -var version string // Populated at build time through `-X main.version=...` -var lwipWriter io.Writer - -func main() { - args.tunAddr = flag.String("tunAddr", "10.0.85.2", "TUN interface IP address") - args.tunGw = flag.String("tunGw", "10.0.85.1", "TUN interface gateway") - args.tunMask = flag.String("tunMask", "255.255.255.0", "TUN interface network mask; prefixlen for IPv6") - args.tunDNS = flag.String("tunDNS", "1.1.1.1,9.9.9.9,208.67.222.222", "Comma-separated list of DNS resolvers for the TUN interface (Windows only)") - args.tunName = flag.String("tunName", "tun0", "TUN interface name") - args.proxyHost = flag.String("proxyHost", "", "Shadowsocks proxy hostname or IP address") - args.proxyPort = flag.Int("proxyPort", 0, "Shadowsocks proxy port number") - args.proxyPassword = flag.String("proxyPassword", "", "Shadowsocks proxy password") - args.proxyCipher = flag.String("proxyCipher", "chacha20-ietf-poly1305", "Shadowsocks proxy encryption cipher") - args.proxyPrefix = flag.String("proxyPrefix", "", "Shadowsocks connection prefix, UTF8-encoded (unsafe)") - args.proxyConfig = flag.String("proxyConfig", "", "A JSON object containing the proxy config, UTF8-encoded") - args.logLevel = flag.String("logLevel", "info", "Logging level: debug|info|warn|error|none") - args.dnsFallback = flag.Bool("dnsFallback", false, "Enable DNS fallback over TCP (overrides the UDP handler).") - args.checkConnectivity = flag.Bool("checkConnectivity", false, "Check the proxy TCP and UDP connectivity and exit.") - args.version = flag.Bool("version", false, "Print the version and exit.") - - flag.Parse() - - if *args.version { - fmt.Println(version) - os.Exit(0) - } - - setLogLevel(*args.logLevel) - - client, err := newShadowsocksClientFromArgs() - if err != nil { - log.Errorf("Failed to create Shadowsocks client: %v", err) - os.Exit(neterrors.IllegalConfiguration.Number()) - } - - if *args.checkConnectivity { - connErrCode, err := shadowsocks.CheckConnectivity(client) - log.Debugf("Connectivity checks error code: %v", connErrCode) - if err != nil { - log.Errorf("Failed to perform connectivity checks: %v", err) - } - os.Exit(connErrCode) - } - - // Open TUN device - dnsResolvers := strings.Split(*args.tunDNS, ",") - tunDevice, err := tun.OpenTunDevice(*args.tunName, *args.tunAddr, *args.tunGw, *args.tunMask, dnsResolvers, persistTun) - if err != nil { - log.Errorf("Failed to open TUN device: %v", err) - os.Exit(neterrors.SystemMisconfigured.Number()) - } - // Output packets to TUN device - core.RegisterOutputFn(tunDevice.Write) - - // Register TCP and UDP connection handlers - core.RegisterTCPConnHandler(tun2socks.NewTCPHandler(client)) - if *args.dnsFallback { - // UDP connectivity not supported, fall back to DNS over TCP. - log.Debugf("Registering DNS fallback UDP handler") - core.RegisterUDPConnHandler(dnsfallback.NewUDPHandler()) - } else { - core.RegisterUDPConnHandler(tun2socks.NewUDPHandler(client, udpTimeout)) - } - - // Configure LWIP stack to receive input data from the TUN device - lwipWriter := core.NewLWIPStack() - go func() { - _, err := io.CopyBuffer(lwipWriter, tunDevice, make([]byte, mtu)) - if err != nil { - log.Errorf("Failed to write data to network stack: %v", err) - os.Exit(neterrors.Unexpected.Number()) - } - }() - - log.Infof("tun2socks running...") - - osSignals := make(chan os.Signal, 1) - signal.Notify(osSignals, os.Interrupt, os.Kill, syscall.SIGTERM, syscall.SIGHUP) - sig := <-osSignals - log.Debugf("Received signal: %v", sig) -} - -func setLogLevel(level string) { - switch strings.ToLower(level) { - case "debug": - log.SetLevel(log.DEBUG) - case "info": - log.SetLevel(log.INFO) - case "warn": - log.SetLevel(log.WARN) - case "error": - log.SetLevel(log.ERROR) - case "none": - log.SetLevel(log.NONE) - default: - log.SetLevel(log.INFO) - } -} - -// newShadowsocksClientFromArgs creates a new shadowsocks.Client instance -// from the global CLI argument object args. -func newShadowsocksClientFromArgs() (*shadowsocks.Client, error) { - if jsonConfig := *args.proxyConfig; len(jsonConfig) > 0 { - return shadowsocks.NewClientFromJSON(jsonConfig) - } else { - // legacy raw flags - config := shadowsocks.Config{ - Host: *args.proxyHost, - Port: *args.proxyPort, - CipherName: *args.proxyCipher, - Password: *args.proxyPassword, - } - if prefixStr := *args.proxyPrefix; len(prefixStr) > 0 { - if p, err := utf8.DecodeUTF8CodepointsToRawBytes(prefixStr); err != nil { - return nil, fmt.Errorf("Failed to parse prefix string: %w", err) - } else { - config.Prefix = p - } - } - return shadowsocks.NewClient(&config) - } -} diff --git a/src/tun2socks/outline/internal/utf8/utf8.go b/src/tun2socks/outline/internal/utf8/utf8.go deleted file mode 100644 index 70058a3a6f..0000000000 --- a/src/tun2socks/outline/internal/utf8/utf8.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2023 The Outline Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// This packages provides helper functions to encode or decode UTF-8 strings -package utf8 - -import "fmt" - -// DecodeUTF8CodepointsToRawBytes parses a UTF-8 string as a raw byte array. -// That is to say, each codepoint in the Unicode string will be treated as a -// single byte (must be in range 0x00 ~ 0xff). -// -// If a codepoint falls out of the range, an error will be returned. -func DecodeUTF8CodepointsToRawBytes(utf8Str string) ([]byte, error) { - runes := []rune(utf8Str) - rawBytes := make([]byte, len(runes)) - for i, r := range runes { - if (r & 0xFF) != r { - return nil, fmt.Errorf("character out of range: %d", r) - } - rawBytes[i] = byte(r) - } - return rawBytes, nil -} diff --git a/src/tun2socks/outline/internal/utf8/utf8_test.go b/src/tun2socks/outline/internal/utf8/utf8_test.go deleted file mode 100644 index 2e3d46c481..0000000000 --- a/src/tun2socks/outline/internal/utf8/utf8_test.go +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2023 The Outline Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package utf8 - -import ( - "bytes" - "testing" -) - -func Test_DecodeUTF8CodepointsToRawBytes(t *testing.T) { - tests := []struct { - name string - input string - want []byte - wantErr bool - }{ - { - name: "basic", - input: "abc 123", - want: []byte{97, 98, 99, 32, 49, 50, 51}, - }, { - name: "empty", - input: "", - want: []byte{}, - }, { - name: "edge cases (explicit)", - input: "\x00\x01\x02 \x7e\x7f \xc2\x80\xc2\x81 \xc3\xbd\xc3\xbf", - // 0xc2+0x80/0x81 will be decoded to 0x80/0x81 (two-byte sequence) - // 0xc3+0xbd/0xbf will be decoded to 0xfd/0xff (two-byte sequence) - want: []byte{0x00, 0x01, 0x02, 32, 0x7e, 0x7f, 32, 0x80, 0x81, 32, 0xfd, 0xff}, - }, { - name: "unicode escapes", - input: "\u0000\u0080\u00ff", - want: []byte{0x00, 0x80, 0xff}, - }, { - name: "edge cases (roundtrip)", - input: string([]rune{0, 1, 2, 126, 127, 128, 129, 254, 255}), - want: []byte{0, 1, 2, 126, 127, 128, 129, 254, 255}, - }, { - name: "out of range 256", - input: string([]rune{256}), - wantErr: true, - }, { - name: "out of range 257", - input: string([]rune{257}), - wantErr: true, - }, { - name: "out of range 65537", - input: string([]rune{65537}), - wantErr: true, - }, { - name: "invalid UTF-8", - input: "\xc3\x28", - wantErr: true, - }, { - name: "invalid Unicode", - input: "\xf8\xa1\xa1\xa1\xa1", - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := DecodeUTF8CodepointsToRawBytes(tt.input) - if (err != nil) != tt.wantErr { - t.Errorf("DecodeCodepointsToBytes() returns error %v, want error %v", err, tt.wantErr) - return - } - if !bytes.Equal(got, tt.want) { - t.Errorf("DecodeCodepointsToBytes() returns %v, want %v", got, tt.want) - } - }) - } -} diff --git a/src/tun2socks/outline/neterrors/neterrors.go b/src/tun2socks/outline/neterrors/neterrors.go deleted file mode 100644 index a95c9ffbfa..0000000000 --- a/src/tun2socks/outline/neterrors/neterrors.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2023 The Outline Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package errors contains a model for errors shared with the Outline Client application. -// -// TODO(fortuna): Revamp error handling. This is an inverted dependency. The Go code should -// provide its own standalone API, leaving translations to the consumer. -package neterrors - -type Error int - -func (e Error) Number() int { - return int(e) -} - -// Outline error codes. Must be kept in sync with definitions in https://github.com/Jigsaw-Code/outline-client/blob/master/src/www/model/errors.ts -const ( - NoError Error = 0 - Unexpected Error = 1 - NoVPNPermissions Error = 2 // Unused - AuthenticationFailure Error = 3 - UDPConnectivity Error = 4 - Unreachable Error = 5 - VpnStartFailure Error = 6 // Unused - IllegalConfiguration Error = 7 // Electron only - ShadowsocksStartFailure Error = 8 // Unused - ConfigureSystemProxyFailure Error = 9 // Unused - NoAdminPermissions Error = 10 // Unused - UnsupportedRoutingTable Error = 11 // Unused - SystemMisconfigured Error = 12 // Electron only -) diff --git a/src/tun2socks/outline/shadowsocks/client.go b/src/tun2socks/outline/shadowsocks/client.go deleted file mode 100644 index a96b004b71..0000000000 --- a/src/tun2socks/outline/shadowsocks/client.go +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright 2022 The Outline Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// This package provides support of Shadowsocks client and the configuration -// that can be used by Outline Client. -// -// All data structures and functions will also be exposed as libraries that -// non-golang callers can use (for example, C/Java/Objective-C). -package shadowsocks - -import ( - "fmt" - "net" - "strconv" - "time" - - "github.com/Jigsaw-Code/outline-client/src/tun2socks/outline" - "github.com/Jigsaw-Code/outline-client/src/tun2socks/outline/connectivity" - "github.com/Jigsaw-Code/outline-client/src/tun2socks/outline/internal/utf8" - "github.com/Jigsaw-Code/outline-sdk/transport" - "github.com/Jigsaw-Code/outline-sdk/transport/shadowsocks" - "github.com/eycorsican/go-tun2socks/common/log" -) - -// A client object that can be used to connect to a remote Shadowsocks proxy. -type Client outline.Client - -// NewClient creates a new Shadowsocks client from a non-nil configuration. -// -// Deprecated: Please use NewClientFromJSON. -func NewClient(config *Config) (*Client, error) { - if config == nil { - return nil, fmt.Errorf("shadowsocks configuration is required") - } - return newShadowsocksClient(config.Host, config.Port, config.CipherName, config.Password, config.Prefix) -} - -// NewClientFromJSON creates a new Shadowsocks client from a JSON formatted -// configuration. -func NewClientFromJSON(configJSON string) (*Client, error) { - config, err := parseConfigFromJSON(configJSON) - if err != nil { - return nil, fmt.Errorf("failed to parse Shadowsocks configuration JSON: %w", err) - } - var prefixBytes []byte = nil - if len(config.Prefix) > 0 { - if p, err := utf8.DecodeUTF8CodepointsToRawBytes(config.Prefix); err != nil { - return nil, fmt.Errorf("failed to parse prefix string: %w", err) - } else { - prefixBytes = p - } - } - return newShadowsocksClient(config.Host, int(config.Port), config.Method, config.Password, prefixBytes) -} - -func newShadowsocksClient(host string, port int, cipherName, password string, prefix []byte) (*Client, error) { - if err := validateConfig(host, port, cipherName, password); err != nil { - return nil, fmt.Errorf("invalid Shadowsocks configuration: %w", err) - } - - // TODO: consider using net.LookupIP to get a list of IPs, and add logic for optimal selection. - proxyIP, err := net.ResolveIPAddr("ip", host) - if err != nil { - return nil, fmt.Errorf("failed to resolve proxy address: %w", err) - } - proxyAddress := net.JoinHostPort(proxyIP.String(), fmt.Sprint(port)) - - cryptoKey, err := shadowsocks.NewEncryptionKey(cipherName, password) - if err != nil { - return nil, fmt.Errorf("failed to create Shadowsocks cipher: %w", err) - } - - streamDialer, err := shadowsocks.NewStreamDialer(&transport.TCPEndpoint{Address: proxyAddress}, cryptoKey) - if err != nil { - return nil, fmt.Errorf("failed to create StreamDialer: %w", err) - } - if len(prefix) > 0 { - log.Debugf("Using salt prefix: %s", string(prefix)) - streamDialer.SaltGenerator = shadowsocks.NewPrefixSaltGenerator(prefix) - } - - packetListener, err := shadowsocks.NewPacketListener(&transport.UDPEndpoint{Address: proxyAddress}, cryptoKey) - if err != nil { - return nil, fmt.Errorf("failed to create PacketListener: %w", err) - } - - return &Client{StreamDialer: streamDialer, PacketListener: packetListener}, nil -} - -// Error number constants exported through gomobile -const ( - NoError = 0 - Unexpected = 1 - NoVPNPermissions = 2 // Unused - AuthenticationFailure = 3 - UDPConnectivity = 4 - Unreachable = 5 - VpnStartFailure = 6 // Unused - IllegalConfiguration = 7 // Electron only - ShadowsocksStartFailure = 8 // Unused - ConfigureSystemProxyFailure = 9 // Unused - NoAdminPermissions = 10 // Unused - UnsupportedRoutingTable = 11 // Unused - SystemMisconfigured = 12 // Electron only -) - -const reachabilityTimeout = 10 * time.Second - -// CheckConnectivity determines whether the Shadowsocks proxy can relay TCP and UDP traffic under -// the current network. Parallelizes the execution of TCP and UDP checks, selects the appropriate -// error code to return accounting for transient network failures. -// Returns an error if an unexpected error ocurrs. -func CheckConnectivity(client *Client) (int, error) { - errCode, err := connectivity.CheckConnectivity((*outline.Client)(client)) - return errCode.Number(), err -} - -// CheckServerReachable determines whether the server at `host:port` is reachable over TCP. -// Returns an error if the server is unreachable. -func CheckServerReachable(host string, port int) error { - conn, err := net.DialTimeout("tcp", net.JoinHostPort(host, strconv.Itoa(port)), reachabilityTimeout) - if err != nil { - return err - } - conn.Close() - return nil -} diff --git a/src/tun2socks/outline/shadowsocks/client_test.go b/src/tun2socks/outline/shadowsocks/client_test.go deleted file mode 100644 index b9800c3d7a..0000000000 --- a/src/tun2socks/outline/shadowsocks/client_test.go +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2023 The Outline Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package shadowsocks - -import "testing" - -func Test_NewClientFromJSON_Errors(t *testing.T) { - tests := []struct { - name string - input string - }{ - { - name: "missing host", - input: `{"port":12345,"method":"some-cipher","password":"abcd1234"}`, - }, - { - name: "missing port", - input: `{"host":"192.0.2.1","method":"some-cipher","password":"abcd1234"}`, - }, - { - name: "missing method", - input: `{"host":"192.0.2.1","port":12345,"password":"abcd1234"}`, - }, - { - name: "missing password", - input: `{"host":"192.0.2.1","port":12345,"method":"some-cipher"}`, - }, - { - name: "empty host", - input: `{"host":"","port":12345,"method":"some-cipher","password":"abcd1234"}`, - }, - { - name: "zero port", - input: `{"host":"192.0.2.1","port":0,"method":"some-cipher","password":"abcd1234"}`, - }, - { - name: "empty method", - input: `{"host":"192.0.2.1","port":12345,"method":"","password":"abcd1234"}`, - }, - { - name: "empty password", - input: `{"host":"192.0.2.1","port":12345,"method":"some-cipher","password":""}`, - }, - { - name: "port -1", - input: `{"host":"192.0.2.1","port":-1,"method":"some-cipher","password":"abcd1234"}`, - }, - { - name: "port 65536", - input: `{"host":"192.0.2.1","port":65536,"method":"some-cipher","password":"abcd1234"}`, - }, - { - name: "prefix out-of-range", - input: `{"host":"192.0.2.1","port":8080,"method":"some-cipher","password":"abcd1234","prefix":"\x1234"}`, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := NewClientFromJSON(tt.input) - if err == nil || got != nil { - t.Errorf("NewClientFromJSON() expects an error, got = %v", got) - return - } - }) - } -} diff --git a/src/tun2socks/outline/shadowsocks/config.go b/src/tun2socks/outline/shadowsocks/config.go deleted file mode 100644 index 4b71248bb9..0000000000 --- a/src/tun2socks/outline/shadowsocks/config.go +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2022 The Outline Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package shadowsocks - -import ( - "encoding/json" - "fmt" -) - -// Config represents a (legacy) shadowsocks server configuration. You can use -// NewClientFromJSON(string) instead. -// -// Deprecated: this object will be removed once we migrated from the old -// Outline Client logic. -type Config struct { - Host string - Port int - Password string - CipherName string - Prefix []byte -} - -// An internal data structure to be used by JSON deserialization. -// Must match the ShadowsocksSessionConfig interface defined in Outline Client. -type configJSON struct { - Host string `json:"host"` - Port uint16 `json:"port"` - Password string `json:"password"` - Method string `json:"method"` - Prefix string `json:"prefix"` -} - -// ParseConfigFromJSON parses a JSON string `in` as a configJSON object. -// The JSON string `in` must match the ShadowsocksSessionConfig interface -// defined in Outline Client. -func parseConfigFromJSON(in string) (*configJSON, error) { - var conf configJSON - if err := json.Unmarshal([]byte(in), &conf); err != nil { - return nil, err - } - return &conf, nil -} - -// validateConfig validates whether a Shadowsocks server configuration is valid -// (it won't do any connectivity tests) -// -// Returns nil if it is valid; or an error message. -func validateConfig(host string, port int, cipher, password string) error { - if len(host) == 0 { - return fmt.Errorf("must provide a host name or IP address") - } - if port <= 0 || port > 65535 { - return fmt.Errorf("port must be within range [1..65535]") - } - if len(cipher) == 0 { - return fmt.Errorf("must provide an encryption cipher method") - } - if len(password) == 0 { - return fmt.Errorf("must provide a password") - } - return nil -} diff --git a/src/tun2socks/outline/shadowsocks/config_test.go b/src/tun2socks/outline/shadowsocks/config_test.go deleted file mode 100644 index 55e86e1578..0000000000 --- a/src/tun2socks/outline/shadowsocks/config_test.go +++ /dev/null @@ -1,217 +0,0 @@ -// Copyright 2023 The Outline Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package shadowsocks - -import ( - "testing" -) - -func Test_parseConfigFromJSON(t *testing.T) { - tests := []struct { - name string - input string - want *configJSON - wantErr bool - }{ - { - name: "normal config", - input: `{"host":"192.0.2.1","port":12345,"method":"some-cipher","password":"abcd1234"}`, - want: &configJSON{ - Host: "192.0.2.1", - Port: 12345, - Method: "some-cipher", - Password: "abcd1234", - Prefix: "", - }, - }, - { - name: "normal config with prefix", - input: `{"host":"192.0.2.1","port":12345,"method":"some-cipher","password":"abcd1234","prefix":"abc 123"}`, - want: &configJSON{ - Host: "192.0.2.1", - Port: 12345, - Method: "some-cipher", - Password: "abcd1234", - Prefix: "abc 123", - }, - }, - { - name: "normal config with extra fields", - input: `{"extra_field":"ignored","host":"192.0.2.1","port":12345,"method":"some-cipher","password":"abcd1234"}`, - want: &configJSON{ - Host: "192.0.2.1", - Port: 12345, - Method: "some-cipher", - Password: "abcd1234", - Prefix: "", - }, - }, - { - name: "unprintable prefix", - input: `{"host":"192.0.2.1","port":12345,"method":"some-cipher","password":"abcd1234","prefix":"abc 123","prefix":"\u0000\u0080\u00ff"}`, - want: &configJSON{ - Host: "192.0.2.1", - Port: 12345, - Method: "some-cipher", - Password: "abcd1234", - Prefix: "\u0000\u0080\u00ff", - }, - }, - { - name: "multi-byte utf-8 prefix", - input: `{"host":"192.0.2.1","port":12345,"method":"some-cipher","password":"abcd1234","prefix":"abc 123","prefix":"` + "\xc2\x80\xc2\x81\xc3\xbd\xc3\xbf" + `"}`, - want: &configJSON{ - Host: "192.0.2.1", - Port: 12345, - Method: "some-cipher", - Password: "abcd1234", - Prefix: "\u0080\u0081\u00fd\u00ff", - }, - }, - { - name: "missing host", - input: `{"port":12345,"method":"some-cipher","password":"abcd1234"}`, - want: &configJSON{ - Host: "", - Port: 12345, - Method: "some-cipher", - Password: "abcd1234", - Prefix: "", - }, - }, - { - name: "missing port", - input: `{"host":"192.0.2.1","method":"some-cipher","password":"abcd1234"}`, - want: &configJSON{ - Host: "192.0.2.1", - Port: 0, - Method: "some-cipher", - Password: "abcd1234", - Prefix: "", - }, - }, - { - name: "missing method", - input: `{"host":"192.0.2.1","port":12345,"password":"abcd1234"}`, - want: &configJSON{ - Host: "192.0.2.1", - Port: 12345, - Method: "", - Password: "abcd1234", - Prefix: "", - }, - }, - { - name: "missing password", - input: `{"host":"192.0.2.1","port":12345,"method":"some-cipher"}`, - want: &configJSON{ - Host: "192.0.2.1", - Port: 12345, - Method: "some-cipher", - Password: "", - Prefix: "", - }, - }, - { - name: "empty host", - input: `{"host":"","port":12345,"method":"some-cipher","password":"abcd1234"}`, - want: &configJSON{ - Host: "", - Port: 12345, - Method: "some-cipher", - Password: "abcd1234", - Prefix: "", - }, - }, - { - name: "zero port", - input: `{"host":"192.0.2.1","port":0,"method":"some-cipher","password":"abcd1234"}`, - want: &configJSON{ - Host: "192.0.2.1", - Port: 0, - Method: "some-cipher", - Password: "abcd1234", - Prefix: "", - }, - }, - { - name: "empty method", - input: `{"host":"192.0.2.1","port":12345,"method":"","password":"abcd1234"}`, - want: &configJSON{ - Host: "192.0.2.1", - Port: 12345, - Method: "", - Password: "abcd1234", - Prefix: "", - }, - }, - { - name: "empty password", - input: `{"host":"192.0.2.1","port":12345,"method":"some-cipher","password":""}`, - want: &configJSON{ - Host: "192.0.2.1", - Port: 12345, - Method: "some-cipher", - Password: "", - Prefix: "", - }, - }, - { - name: "empty prefix", - input: `{"host":"192.0.2.1","port":12345,"method":"some-cipher","password":"abcd1234","prefix":""}`, - want: &configJSON{ - Host: "192.0.2.1", - Port: 12345, - Method: "some-cipher", - Password: "abcd1234", - Prefix: "", - }, - }, - { - name: "port -1", - input: `{"host":"192.0.2.1","port":-1,"method":"some-cipher","password":"abcd1234"}`, - wantErr: true, - }, - { - name: "port 65536", - input: `{"host":"192.0.2.1","port":65536,"method":"some-cipher","password":"abcd1234"}`, - wantErr: true, - }, - { - name: "prefix out-of-range", - input: `{"host":"192.0.2.1","port":8080,"method":"some-cipher","password":"abcd1234","prefix":"\x1234"}`, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := parseConfigFromJSON(tt.input) - if (err != nil) != tt.wantErr { - t.Errorf("ParseConfigFromJSON() error = %v, wantErr %v", err, tt.wantErr) - return - } - if tt.wantErr { - return - } - if got.Host != tt.want.Host || - got.Port != tt.want.Port || - got.Method != tt.want.Method || - got.Password != tt.want.Password || - got.Prefix != tt.want.Prefix { - t.Errorf("ParseConfigFromJSON() = %v (prefix %+q), want %v (prefix %+q)", got, got.Prefix, tt.want, tt.want.Prefix) - } - }) - } -} diff --git a/src/tun2socks/outline/tun2socks/tcp.go b/src/tun2socks/outline/tun2socks/tcp.go deleted file mode 100644 index 1bb6bd2c88..0000000000 --- a/src/tun2socks/outline/tun2socks/tcp.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2023 The Outline Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tun2socks - -import ( - "context" - "io" - "net" - - "github.com/Jigsaw-Code/outline-sdk/transport" - "github.com/eycorsican/go-tun2socks/core" -) - -type tcpHandler struct { - dialer transport.StreamDialer -} - -// NewTCPHandler returns a Shadowsocks TCP connection handler. -func NewTCPHandler(client transport.StreamDialer) core.TCPConnHandler { - return &tcpHandler{client} -} - -func (h *tcpHandler) Handle(conn net.Conn, target *net.TCPAddr) error { - proxyConn, err := h.dialer.Dial(context.Background(), target.String()) - if err != nil { - return err - } - // TODO: Request upstream to make `conn` a `core.TCPConn` so we can avoid this type assertion. - go relay(conn.(core.TCPConn), proxyConn) - return nil -} - -func copyOneWay(leftConn, rightConn transport.StreamConn) (int64, error) { - n, err := io.Copy(leftConn, rightConn) - // Send FIN to indicate EOF - leftConn.CloseWrite() - // Release reader resources - rightConn.CloseRead() - return n, err -} - -// relay copies between left and right bidirectionally. Returns number of -// bytes copied from right to left, from left to right, and any error occurred. -// Relay allows for half-closed connections: if one side is done writing, it can -// still read all remaining data from its peer. -func relay(leftConn, rightConn transport.StreamConn) (int64, int64, error) { - type res struct { - N int64 - Err error - } - ch := make(chan res) - - go func() { - n, err := copyOneWay(rightConn, leftConn) - ch <- res{n, err} - }() - - n, err := copyOneWay(leftConn, rightConn) - rs := <-ch - - if err == nil { - err = rs.Err - } - return n, rs.N, err -} diff --git a/src/tun2socks/outline/tun2socks/tunnel.go b/src/tun2socks/outline/tun2socks/tunnel.go deleted file mode 100644 index 5944373ab8..0000000000 --- a/src/tun2socks/outline/tun2socks/tunnel.go +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2019 The Outline Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tun2socks - -import ( - "errors" - "io" - "net" - "time" - - "github.com/eycorsican/go-tun2socks/core" - "github.com/eycorsican/go-tun2socks/proxy/dnsfallback" - - "github.com/Jigsaw-Code/outline-sdk/transport" - - "github.com/Jigsaw-Code/outline-client/src/tun2socks/outline/connectivity" - "github.com/Jigsaw-Code/outline-client/src/tun2socks/tunnel" -) - -// Tunnel represents a tunnel from a TUN device to a server. -type Tunnel interface { - tunnel.Tunnel - - // UpdateUDPSupport determines if UDP is supported following a network connectivity change. - // Sets the tunnel's UDP connection handler accordingly, falling back to DNS over TCP if UDP is not supported. - // Returns whether UDP proxying is supported in the new network. - UpdateUDPSupport() bool -} - -// Deprecated: use Tunnel directly. -type OutlineTunnel = Tunnel - -type outlinetunnel struct { - tunnel.Tunnel - lwipStack core.LWIPStack - streamDialer transport.StreamDialer - packetDialer transport.PacketListener - isUDPEnabled bool // Whether the tunnel supports proxying UDP. -} - -// newTunnel connects a tunnel to a Shadowsocks proxy server and returns an `outline.Tunnel`. -// -// `host` is the IP or domain of the Shadowsocks proxy. -// `port` is the port of the Shadowsocks proxy. -// `password` is the password of the Shadowsocks proxy. -// `cipher` is the encryption cipher used by the Shadowsocks proxy. -// `isUDPEnabled` indicates if the Shadowsocks proxy and the network support proxying UDP traffic. -// `tunWriter` is used to output packets back to the TUN device. OutlineTunnel.Disconnect() will close `tunWriter`. -func newTunnel(streamDialer transport.StreamDialer, packetDialer transport.PacketListener, isUDPEnabled bool, tunWriter io.WriteCloser) (Tunnel, error) { - if tunWriter == nil { - return nil, errors.New("Must provide a TUN writer") - } - core.RegisterOutputFn(func(data []byte) (int, error) { - return tunWriter.Write(data) - }) - lwipStack := core.NewLWIPStack() - base := tunnel.NewTunnel(tunWriter, lwipStack) - t := &outlinetunnel{base, lwipStack, streamDialer, packetDialer, isUDPEnabled} - t.registerConnectionHandlers() - return t, nil -} - -func (t *outlinetunnel) UpdateUDPSupport() bool { - resolverAddr := &net.UDPAddr{IP: net.ParseIP("1.1.1.1"), Port: 53} - isUDPEnabled := connectivity.CheckUDPConnectivityWithDNS(t.packetDialer, resolverAddr) == nil - if t.isUDPEnabled != isUDPEnabled { - t.isUDPEnabled = isUDPEnabled - t.lwipStack.Close() // Close existing connections to avoid using the previous handlers. - t.registerConnectionHandlers() - } - return isUDPEnabled -} - -// Registers UDP and TCP Shadowsocks connection handlers to the tunnel's host and port. -// Registers a DNS/TCP fallback UDP handler when UDP is disabled. -func (t *outlinetunnel) registerConnectionHandlers() { - var udpHandler core.UDPConnHandler - if t.isUDPEnabled { - udpHandler = NewUDPHandler(t.packetDialer, 30*time.Second) - } else { - udpHandler = dnsfallback.NewUDPHandler() - } - core.RegisterTCPConnHandler(NewTCPHandler(t.streamDialer)) - core.RegisterUDPConnHandler(udpHandler) -} diff --git a/src/tun2socks/outline/tun2socks/tunnel_android.go b/src/tun2socks/outline/tun2socks/tunnel_android.go deleted file mode 100644 index c9792fc858..0000000000 --- a/src/tun2socks/outline/tun2socks/tunnel_android.go +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2019 The Outline Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tun2socks - -import ( - "runtime/debug" - - "github.com/Jigsaw-Code/outline-client/src/tun2socks/outline/shadowsocks" - "github.com/Jigsaw-Code/outline-client/src/tun2socks/tunnel" - "github.com/eycorsican/go-tun2socks/common/log" -) - -func init() { - // Conserve memory by increasing garbage collection frequency. - debug.SetGCPercent(10) - log.SetLevel(log.WARN) -} - -// ConnectShadowsocksTunnel reads packets from a TUN device and routes it to a Shadowsocks proxy server. -// Returns an OutlineTunnel instance and does *not* take ownership of the TUN file descriptor; the -// caller is responsible for closing after OutlineTunnel disconnects. -// -// - `fd` is the TUN device. The OutlineTunnel acquires an additional reference to it, which -// is released by OutlineTunnel.Disconnect(), so the caller must close `fd` _and_ call -// Disconnect() in order to close the TUN device. -// - `client` is the Shadowsocks client (created by [shadowsocks.NewClient]). -// - `isUDPEnabled` indicates whether the tunnel and/or network enable UDP proxying. -// -// Returns an error if the TUN file descriptor cannot be opened, or if the tunnel fails to -// connect. -func ConnectShadowsocksTunnel(fd int, client *shadowsocks.Client, isUDPEnabled bool) (Tunnel, error) { - tun, err := tunnel.MakeTunFile(fd) - if err != nil { - return nil, err - } - t, err := newTunnel(client, client, isUDPEnabled, tun) - if err != nil { - return nil, err - } - go tunnel.ProcessInputPackets(t, tun) - return t, nil -} diff --git a/src/tun2socks/outline/tun2socks/tunnel_darwin.go b/src/tun2socks/outline/tun2socks/tunnel_darwin.go deleted file mode 100644 index 2caf3d06a8..0000000000 --- a/src/tun2socks/outline/tun2socks/tunnel_darwin.go +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2019 The Outline Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tun2socks - -import ( - "errors" - "io" - "runtime/debug" - "time" - - "github.com/Jigsaw-Code/outline-client/src/tun2socks/outline/shadowsocks" -) - -// TunWriter is an interface that allows for outputting packets to the TUN (VPN). -type TunWriter interface { - io.WriteCloser -} - -func init() { - // Apple VPN extensions have a memory limit of 15MB. Conserve memory by increasing garbage - // collection frequency and returning memory to the OS every minute. - debug.SetGCPercent(10) - // TODO: Check if this is still needed in go 1.13, which returns memory to the OS - // automatically. - ticker := time.NewTicker(time.Minute * 1) - go func() { - for range ticker.C { - debug.FreeOSMemory() - } - }() -} - -// ConnectShadowsocksTunnel reads packets from a TUN device and routes it to a Shadowsocks proxy server. -// Returns an OutlineTunnel instance that should be used to input packets to the tunnel. -// -// `tunWriter` is used to output packets to the TUN (VPN). -// `client` is the Shadowsocks client (created by [shadowsocks.NewClient]). -// `isUDPEnabled` indicates whether the tunnel and/or network enable UDP proxying. -// -// Sets an error if the tunnel fails to connect. -func ConnectShadowsocksTunnel(tunWriter TunWriter, client *shadowsocks.Client, isUDPEnabled bool) (Tunnel, error) { - if tunWriter == nil { - return nil, errors.New("must provide a TunWriter") - } else if client == nil { - return nil, errors.New("must provide a client") - } - return newTunnel(client, client, isUDPEnabled, tunWriter) -} diff --git a/src/tun2socks/outline/tun2socks/udp.go b/src/tun2socks/outline/tun2socks/udp.go deleted file mode 100644 index cd48d7d691..0000000000 --- a/src/tun2socks/outline/tun2socks/udp.go +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright 2023 The Outline Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tun2socks - -import ( - "context" - "fmt" - "net" - "sync" - "time" - - "github.com/Jigsaw-Code/outline-sdk/transport" - "github.com/eycorsican/go-tun2socks/core" -) - -type udpHandler struct { - // Protects the connections map - sync.Mutex - - // Used to establish connections to the proxy - listener transport.PacketListener - - // How long to wait for a packet from the proxy. Longer than this and the connection - // is closed. - timeout time.Duration - - // Maps connections from TUN to connections to the proxy. - conns map[core.UDPConn]net.PacketConn -} - -// NewUDPHandler returns a Shadowsocks UDP connection handler. -// -// `client` provides the Shadowsocks functionality. -// `timeout` is the UDP read and write timeout. -func NewUDPHandler(dialer transport.PacketListener, timeout time.Duration) core.UDPConnHandler { - return &udpHandler{ - listener: dialer, - timeout: timeout, - conns: make(map[core.UDPConn]net.PacketConn, 8), - } -} - -func (h *udpHandler) Connect(tunConn core.UDPConn, target *net.UDPAddr) error { - proxyConn, err := h.listener.ListenPacket(context.Background()) - if err != nil { - return err - } - h.Lock() - h.conns[tunConn] = proxyConn - h.Unlock() - go h.relayPacketsFromProxy(tunConn, proxyConn) - return nil -} - -// relayPacketsFromProxy relays packets from the proxy to the TUN device. -func (h *udpHandler) relayPacketsFromProxy(tunConn core.UDPConn, proxyConn net.PacketConn) { - buf := core.NewBytes(core.BufSize) - defer func() { - h.close(tunConn) - core.FreeBytes(buf) - }() - for { - proxyConn.SetDeadline(time.Now().Add(h.timeout)) - n, sourceAddr, err := proxyConn.ReadFrom(buf) - if err != nil { - return - } - // No resolution will take place, the address sent by the proxy is a resolved IP. - sourceUDPAddr, err := net.ResolveUDPAddr("udp", sourceAddr.String()) - if err != nil { - return - } - _, err = tunConn.WriteFrom(buf[:n], sourceUDPAddr) - if err != nil { - return - } - } -} - -// ReceiveTo relays packets from the TUN device to the proxy. It's called by tun2socks. -func (h *udpHandler) ReceiveTo(tunConn core.UDPConn, data []byte, destAddr *net.UDPAddr) error { - h.Lock() - proxyConn, ok := h.conns[tunConn] - h.Unlock() - if !ok { - return fmt.Errorf("connection %v->%v does not exist", tunConn.LocalAddr(), destAddr) - } - proxyConn.SetDeadline(time.Now().Add(h.timeout)) - _, err := proxyConn.WriteTo(data, destAddr) - return err -} - -func (h *udpHandler) close(tunConn core.UDPConn) { - tunConn.Close() - h.Lock() - defer h.Unlock() - if proxyConn, ok := h.conns[tunConn]; ok { - proxyConn.Close() - } -} diff --git a/src/tun2socks/tunnel/tun.go b/src/tun2socks/tunnel/tun.go deleted file mode 100644 index 3532c7bb91..0000000000 --- a/src/tun2socks/tunnel/tun.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2019 The Outline Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tunnel - -import ( - "os" - - "github.com/eycorsican/go-tun2socks/common/log" - _ "github.com/eycorsican/go-tun2socks/common/log/simple" // Import simple log for the side effect of making logs printable. -) - -const vpnMtu = 1500 - -// ProcessInputPackets reads packets from a TUN device `tun` and writes them to `tunnel`. -func ProcessInputPackets(tunnel Tunnel, tun *os.File) { - buffer := make([]byte, vpnMtu) - for tunnel.IsConnected() { - len, err := tun.Read(buffer) - if err != nil { - log.Warnf("Failed to read packet from TUN: %v", err) - continue - } - if len == 0 { - log.Infof("Read EOF from TUN") - continue - } - tunnel.Write(buffer) - } -} diff --git a/src/tun2socks/tunnel/tun_android.go b/src/tun2socks/tunnel/tun_android.go deleted file mode 100644 index 9cc0beae02..0000000000 --- a/src/tun2socks/tunnel/tun_android.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2019 The Outline Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build android - -package tunnel - -import ( - "errors" - "os" - - _ "github.com/eycorsican/go-tun2socks/common/log/simple" // Import simple log for the side effect of making logs printable. - "golang.org/x/sys/unix" -) - -// MakeTunFile returns an os.File object from a TUN file descriptor `fd`. -// The returned os.File holds a separate reference to the underlying file, -// so the file will not be closed until both `fd` and the os.File are -// separately closed. (UNIX only.) -func MakeTunFile(fd int) (*os.File, error) { - if fd < 0 { - return nil, errors.New("Must provide a valid TUN file descriptor") - } - // Make a copy of `fd` so that os.File's finalizer doesn't close `fd`. - newfd, err := unix.Dup(fd) - if err != nil { - return nil, err - } - file := os.NewFile(uintptr(newfd), "") - if file == nil { - return nil, errors.New("Failed to open TUN file descriptor") - } - return file, nil -} diff --git a/src/tun2socks/tunnel/tunnel.go b/src/tun2socks/tunnel/tunnel.go deleted file mode 100644 index 28c2b4c0d9..0000000000 --- a/src/tun2socks/tunnel/tunnel.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2019 The Outline Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tunnel - -import ( - "errors" - "io" - - "github.com/eycorsican/go-tun2socks/core" -) - -// Tunnel represents a session on a TUN device. -type Tunnel interface { - // IsConnected is true if Disconnect has not been called. - IsConnected() bool - // Disconnect closes the underlying resources. Subsequent Write calls will fail. - Disconnect() - // Write writes input data to the TUN interface. - Write(data []byte) (int, error) -} - -type tunnel struct { - tunWriter io.WriteCloser - lwipStack core.LWIPStack - isConnected bool -} - -func (t *tunnel) IsConnected() bool { - return t.isConnected -} - -func (t *tunnel) Disconnect() { - if !t.isConnected { - return - } - t.isConnected = false - t.lwipStack.Close() - t.tunWriter.Close() -} - -func (t *tunnel) Write(data []byte) (int, error) { - if !t.isConnected { - return 0, errors.New("Failed to write, network stack closed") - } - return t.lwipStack.Write(data) -} - -func NewTunnel(tunWriter io.WriteCloser, lwipStack core.LWIPStack) Tunnel { - return &tunnel{tunWriter, lwipStack, true} -} diff --git a/third_party/outline-go-tun2socks/METADATA b/third_party/outline-go-tun2socks/METADATA index 50a9d77a15..31737f780a 100644 --- a/third_party/outline-go-tun2socks/METADATA +++ b/third_party/outline-go-tun2socks/METADATA @@ -8,4 +8,4 @@ third_party { } version: "2.2.1" last_upgrade_date { year: 2022 month: 4 day: 20 } -} \ No newline at end of file +} diff --git a/third_party/outline-go-tun2socks/linux/tun2socks b/third_party/outline-go-tun2socks/linux/tun2socks new file mode 100755 index 0000000000..0e07ba3fd1 Binary files /dev/null and b/third_party/outline-go-tun2socks/linux/tun2socks differ diff --git a/third_party/outline-go-tun2socks/win32/tun2socks.exe b/third_party/outline-go-tun2socks/win32/tun2socks.exe new file mode 100755 index 0000000000..76a6f0cf63 Binary files /dev/null and b/third_party/outline-go-tun2socks/win32/tun2socks.exe differ diff --git a/tools.go b/tools.go deleted file mode 100644 index 39aa39188f..0000000000 --- a/tools.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2019 The Outline Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build tools -// +build tools - -// See https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module -// and https://github.com/go-modules-by-example/index/blob/master/010_tools/README.md - -package tools - -import ( - _ "github.com/crazy-max/xgo" - _ "golang.org/x/mobile/cmd/gomobile" - _ "golang.org/x/mobile/cmd/gobind" -)