Compare commits

...
163 Commits
Author SHA1 Message Date
wweir 91582d6095 Change domain detect logic 2020-04-18 20:39:40 +08:00
wweir 396b08c0ac Migrate to white list match 2020-04-16 17:44:50 +08:00
wweir 78b6d4df2e Fix disable http_proxy case 2020-03-25 10:03:33 +08:00
wweir 1574623507 Optimize router logic 2020-03-23 12:04:14 +08:00
wweir ee6335fa47 Keep file mod for config file 2020-03-23 07:18:15 +08:00
wweir f7214bfdb4 Optimize code 2020-03-22 16:49:40 +08:00
wweir 291ae3f85a Optimize CI workflow 2020-03-21 11:51:12 +08:00
wweir 07c51d42ff Support windows platform 2020-03-21 11:35:19 +08:00
wweir 90c497b8da Optimize router 2020-03-21 10:50:41 +08:00
wweir 28bd12d220 Merge branch 'refactor/esni' 2020-03-19 16:59:43 +08:00
wweir c0f39fc2e0 Refactor for tls 1.3 esni 2020-03-19 16:38:08 +08:00
wweir bcc6d7c504 Fix the last dot in domain match 2020-02-23 08:08:04 +08:00
wweir 5d1827b2da Optimize dynamic detect logic 2020-02-18 17:40:14 +08:00
wweir c38d519f92 Fix CI 2020-02-17 09:07:01 +08:00
wweir dd4444b71c Merge branch 'feature/router' 2020-02-17 06:56:59 +08:00
wweir 899d64a92c Update readme 2020-02-17 06:56:34 +08:00
wweir 7d77f4a85e Add Socks5 support 2020-02-17 06:19:36 +08:00
wweir 4681dd9845 Debug 2020-02-16 16:04:58 +08:00
wweir 85226890c5 Support run as system service 2020-02-16 05:57:44 +08:00
wweir 1a24286bbc Refactor to tls 2020-02-15 15:52:15 +08:00
wweir fd5c34dca1 Refactor to proxy router 2020-02-05 18:00:09 +08:00
wweir 8cd4fe57ba Fix github action badge 2019-10-10 14:06:07 +08:00
wweir 545b153f50 FIx #18, support ipv6 2019-09-28 17:54:28 +08:00
wweir 697e2aa1d5 Fix #18, support custom
upstream dns port
2019-09-28 13:15:47 +08:00
wweir 2fe1708d3f Fix #19, update shorten url 2019-09-28 13:09:15 +08:00
wweir cbe943a020 Migrate ci from circleci to github actions 2019-09-26 07:52:29 +08:00
wweir d7e25d6565 Update dependency 2019-09-26 06:51:00 +08:00
wweir acb3f58526 Update go.yml 2019-09-11 19:01:24 +08:00
wweir 50684272c9 Update go.yml 2019-09-11 18:13:48 +08:00
wweir b0428177de Support Windows install with relative path 2019-06-24 10:41:46 +08:00
wweir 75e7f1aab7 Update dependency 2019-05-31 14:36:11 +08:00
wweir f256ab686d Fix default config file path 2019-05-05 09:32:23 +08:00
wweir 0c0f0699c1 Change config 2019-04-18 14:03:53 +08:00
wweir 8532a2e16c Merge branch 'feature/more-protocol' 2019-04-16 08:08:26 +08:00
wweir bad338033c Fix code 2019-04-16 08:04:37 +08:00
wweir a57f85ab1b Changes for unit test 2019-04-04 10:57:25 +08:00
wweir aed2afbe6d Fix protocol 2019-04-04 10:57:25 +08:00
wweir 2becd42176 Fix protocol 2019-03-31 08:06:00 +08:00
wweir d3664dc2b9 Add more protocols support 2019-03-30 12:31:02 +08:00
wweir a6fd60b5a2 Fix proxy port 2019-03-30 07:33:10 +08:00
wweir d6dbfd4129 Fix windows refresh hook cmd 2019-03-28 09:51:45 +08:00
wweir d31bbf9226 Fix #12, add print version flag 2019-03-27 21:22:52 +08:00
wweir ac4e4266bc Fix deploy script for #12 2019-03-27 15:36:49 +08:00
wweir f9f0ca4ee7 Merge branch 'bug/suggest' 2019-03-27 15:23:32 +08:00
wweir 98c3934762 Optimize suggest trigger 2019-03-27 15:22:25 +08:00
wweir f001f7dab7 Precompile more platform binary file 2019-03-24 18:44:27 +08:00
wweir 36ef329b68 Update todo list 2019-03-24 18:40:34 +08:00
wweir 64778f68ac Merge branch 'feature/socsk5' 2019-03-24 00:16:02 +08:00
wweir 53748cfa85 Support set socsk5 as upstream proxy 2019-03-24 00:15:08 +08:00
wweir d35115b7b3 Fix for vet 2019-03-20 08:48:46 +08:00
wweir 559c5fde6b Merge branch 'feature/dhcp' 2019-03-20 08:45:50 +08:00
wweir eb8080df18 Avoid unnecessary dhcp message 2019-03-20 08:45:37 +08:00
wweir 45ca063a7e Add timeout for dns exchange 2019-03-19 13:30:36 +08:00
wweir 6add7afd19 Fix for go modules 2019-03-15 11:46:51 +08:00
wweir f13687506e Merge branch 'feature/suggest-level' 2019-03-14 08:41:50 +08:00
wweir 199e4cf295 For #11, expose suggest level option 2019-03-14 08:38:18 +08:00
wweir b3ad9f6657 Fix panic in special network environment 2019-03-12 11:53:58 +08:00
wweir f9e9fe366f Fix log message 2019-03-12 11:53:58 +08:00
wweir 103699e25e Add docker build badge 2019-03-11 10:25:43 +08:00
wweir f80755eae5 Shorten install file url 2019-03-11 07:31:27 +08:00
wweir 5b217b794d Merge branch 'refactor/codes' 2019-03-11 00:22:15 +08:00
wweir 26055c5203 Refactor deploy 2019-03-11 00:11:07 +08:00
wweir 029611c078 Refactor proxy 2019-03-11 00:09:02 +08:00
wweir 89adc6a2c9 Change code style 2019-03-10 21:03:11 +08:00
wweir 8c95c59e5e Merge branch 'bug/dhcp' 2019-03-10 13:28:50 +08:00
wweir 4f8376c041 Fix #7, refactor dhcp implementation for dynamic DNS on Windows 2019-03-10 13:26:33 +08:00
wweir 10172f8be2 Build docker image in docker hub 2019-03-08 21:55:18 +08:00
wweir 2831cdf284 Add Windows flush dns cmd 2019-03-07 07:43:22 +08:00
wweir 06558ac635 Optimize http ping score logic 2019-03-06 11:06:37 +08:00
wweir 3cf4deddb7 Merge branch 'bug/ci-windows' 2019-03-06 08:13:10 +08:00
wweir 679263be9a Fix #8, fix ci script 2019-03-06 08:12:48 +08:00
wweir 21d8ec4ec7 Merge branch 'refactor/http-ping' 2019-03-06 07:44:17 +08:00
wweir 4916245522 Optimize http ping logic 2019-03-06 07:43:45 +08:00
wweir 11c2808320 Add description 2019-03-02 10:25:17 +08:00
wweir 21ddd79e4d Fix deploy script 2019-03-02 09:29:21 +08:00
wweir 9a158d4088 Optimize QUIC paramaters 2019-03-02 09:22:31 +08:00
wweir 88990cddd9 Update dependency 2019-03-02 09:07:20 +08:00
wweir ca1b2650ab Optimize install script 2019-03-02 08:07:01 +08:00
wweir 8ff1046ebf Merge branch 'bug/suffix-tree' 2019-02-28 17:38:52 +08:00
wweir dfe81f6f23 Fix suffix tree match logic 2019-02-28 17:38:34 +08:00
wweir 972875ec36 Optimize default rules 2019-02-28 16:14:40 +08:00
wweir 89b7a41d46 Add rule for mail 2019-02-28 15:40:57 +08:00
wweir 8f598dbc17 Optimize ci workflow 2019-02-27 14:28:54 +08:00
wweir 192b4f798b Optimize suggest logic 2019-02-27 14:16:02 +08:00
wweir 2adefe0dfc Fix clear dns command for windows 2019-02-26 17:33:39 +08:00
wweir 7b0c13ef1c Merge branch 'refactor/watch-config' 2019-02-24 18:20:31 +08:00
wweir 63bb934148 Release config file immediatly 2019-02-24 18:18:43 +08:00
wweir cacc732a4e Remove fsnotify, add refresh trigger for config 2019-02-24 17:43:17 +08:00
wweir 17d079dcb2 Fix #3, Merge branch 'feature/windows_svc' 2019-02-24 16:12:05 +08:00
wweir 413a93f6f7 Support Windows service deploy 2019-02-24 16:10:19 +08:00
wweir 6c5f5cd74f Add windows service support 2019-02-24 15:58:38 +08:00
wweir e948541efe Merge branch 'chore/code' 2019-02-19 16:53:21 +08:00
wweir ffa2128274 Move code files path 2019-02-19 16:53:08 +08:00
wweir b53b7874b6 Update README.md 2019-02-19 16:24:47 +08:00
wweir f3117edfa0 For macOS without brew 2019-02-19 13:04:36 +08:00
wweir ba8980da73 Limit dns detect timeout short than 1s for multi dns setting 2019-02-17 07:29:23 +08:00
wweir e3ec74b74f Merge branch 'bug/resolve-addr' 2019-02-16 22:41:28 +08:00
wweir 6e67193894 Release on tag 2019-02-16 22:40:54 +08:00
wweir af0c5f895c Fix resolve for dhcp 2019-02-16 22:26:20 +08:00
wweir 4883e240cc Merge branch 'feature/score' @release 2019-02-15 22:48:06 +08:00
wweir 18ee9ca993 Refactor intelligent detect to score logic 2019-02-15 22:47:51 +08:00
wweir 4fff2a1f3b Merge branch 'bug/dhcp-linux' @release 2019-02-15 00:28:53 +08:00
wweir 3e82890c33 Fix dhcp conflict with dhcpcd and dhclient 2019-02-15 00:27:03 +08:00
wweir 01d66d737b Merge branch 'bug/suggest' @release 2019-02-13 21:08:00 +08:00
wweir e342dadaf1 Fix http ping logic for suggestions 2019-02-13 21:07:40 +08:00
wweir 7ba9f1a23c Fix intelligent detect @release 2019-02-11 20:06:21 +08:00
wweir 7240208cb3 Merge branch 'bug/dhcp' @release 2019-02-02 08:35:33 +08:00
wweir a27748d835 Fix race 2019-02-02 08:35:16 +08:00
wweir 39797d077b Merge branch 'feature/dhcp' @release 2019-02-02 06:22:29 +08:00
wweir b6df69358a Fix #2, add dhcp dynamic setting upstream dns, ipv4 only 2019-02-02 06:21:51 +08:00
wweir ae525f9b40 Fix wait timeout 2019-01-31 07:36:41 +08:00
wweir 29eb0e6e1c Fix toml spell issue 2019-01-31 06:51:57 +08:00
wweir aef941b17e Add a reminder to check the configuration 2019-01-31 06:50:26 +08:00
wweir 9bc5a32074 Add more fuzz match support @release 2019-01-29 13:34:52 +08:00
wweir 8194083f54 Add more fuzz rules support @release 2019-01-28 22:41:07 +08:00
wweir c20903137d Fix #5, add ipv6 support @release 2019-01-28 16:54:27 +08:00
wweir 2f3c98b8d3 Clean code 2019-01-27 08:35:12 +08:00
wweir bc99bda1b3 Clear unnessary block rule 2019-01-25 23:13:48 +08:00
wweir ceedef4203 Add https ping @release 2019-01-25 22:46:28 +08:00
wweir 712b689708 Force enable go modules 2019-01-25 16:13:22 +08:00
wweir d5260d66df Fix match rule 2019-01-25 15:27:56 +08:00
wweir bde27f64ec Fix 433 detect 2019-01-23 22:31:37 +08:00
wweir 04193eb608 Change intelligent detect logic @release 2019-01-23 20:10:41 +08:00
wweir 41a1adc707 Fix intelligent detect logic @release 2019-01-23 12:52:34 +08:00
wweir c9191ec928 Fix intelligent detect logic @release 2019-01-23 11:36:34 +08:00
wweir 14da5b711e Fix intelligent logic @release 2019-01-23 07:33:28 +08:00
wweir 59257c0673 Add description about intelligent detect @release 2019-01-22 22:25:45 +08:00
wweir 7db92a19be Fix intelligent detect logic @release 2019-01-22 22:08:59 +08:00
wweir fbae2999dc Change deploy script to keep suggestions @release 2019-01-22 21:24:33 +08:00
wweir 92991da6a0 Fix rule for github network speed 2019-01-22 21:16:25 +08:00
wweir b60b5fceb3 Fix deadloop in wildcard dns setting case @release 2019-01-22 20:54:27 +08:00
wweir 756a8c8d17 Add intelligent rule suggestions @release 2019-01-22 20:12:47 +08:00
wweir e02da26936 Refactor for intelligent dns rule 2019-01-21 13:55:55 +08:00
wweir c5adcc4421 Fix permission @release 2019-01-21 11:22:38 +08:00
wweir c20c3621c6 Change release filenames @release 2019-01-21 11:19:31 +08:00
wweir 055c1a2901 Fix conn leak @release 2019-01-21 10:57:48 +08:00
wweir fc2ea8b8c0 Update default config file path 2019-01-20 21:41:29 +08:00
wweir 3e6b9e96ca Add docs link 2019-01-20 17:47:57 +08:00
wweir 2264d73c45 Add docker deploy description 2019-01-19 18:52:34 +08:00
wweir 99ba06b2d4 Add auto deploy and undeploy description 2019-01-19 18:40:02 +08:00
wweir a8e026ff7f For user friendly @release 2019-01-19 16:07:55 +08:00
wweir 9e377e4a2d Add deploy script for macOS and Linux platform 2019-01-19 15:33:00 +08:00
wweir 107174fb6b Fix badge link 2019-01-19 12:55:57 +08:00
wweir 3f8f5c2ef1 Fix badge display 2019-01-19 12:43:21 +08:00
wweir 918b964b78 Fix CI docker image tag 2019-01-19 12:37:05 +08:00
wweir 34722a460c Fix docker image and add release toggle option 2019-01-19 12:13:12 +08:00
wweir 8734486c48 Add CI release support 2019-01-19 11:58:33 +08:00
wweir 8d0ee2a9ea Add ci scripts 2019-01-19 11:03:32 +08:00
wweir 98247845ad Clean code 2019-01-19 08:10:11 +08:00
wweir 72dacaf1de Fix telegram config 2019-01-16 23:24:10 +08:00
wweir 56d5090841 Separate http_proxy interface setting for some case 2019-01-16 22:37:18 +08:00
wweir 821cbc302e Add http proxy support 2019-01-16 18:42:03 +08:00
wweir f6eddb9f96 For #1, support for specified a network interface 2019-01-16 14:40:06 +08:00
wweir 344531c301 Clean code 2019-01-16 07:26:59 +08:00
wweir df897f72c6 Fix quic session close 2019-01-15 22:55:50 +08:00
wweir dd2fe95900 update readme architecture 2019-01-15 19:15:06 +08:00
wweir 08a5d4363b Fix dead loop 2019-01-15 19:06:16 +08:00
wweir e712dcb78c Add multi cipher support 2019-01-15 19:00:12 +08:00
wweir e9f5fc4789 Extend quic idle timeout 2019-01-15 08:26:06 +08:00
wweir f76f31d054 Update dependency 2019-01-15 08:25:48 +08:00
wweir e0de537042 Clean dirty code 2019-01-15 08:04:54 +08:00
wweir b050e312b2 Redesign shadow payload 2019-01-12 14:32:44 +08:00
wweir d78a757200 Fix shadow conn 2019-01-12 13:53:13 +08:00
54 changed files with 2431 additions and 1472 deletions
+17
View File
@@ -0,0 +1,17 @@
# Compile
FROM golang:1.14-alpine AS compiler
RUN apk add --no-cache git make
# enable go modules
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 make build
# Build image
FROM scratch
COPY --from=compiler /src/sower /sower
ENTRYPOINT [ "/sower" ]
+16
View File
@@ -0,0 +1,16 @@
name: Go
on:
push:
branches:
- master
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v2-beta
- uses: actions/checkout@v2
- name: test and build
run: |
make
+53
View File
@@ -0,0 +1,53 @@
name: Release
on:
push:
tags:
- "v*.*.*"
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v2-beta
- uses: actions/checkout@v2
- name: build matrix
run: |
cp conf/sower.toml sower.toml
GOOS=linux GOARCH=amd64 make build
tar czvf sower-linux-amd64.tar.gz sower sower.toml
GOOS=linux GOARCH=386 make build
tar czvf sower-linux-386.tar.gz sower sower.toml
GOOS=linux GOARCH=arm make build
tar czvf sower-linux-arm.tar.gz sower sower.toml
GOOS=linux GOARCH=arm64 make build
tar czvf sower-linux-arm64.tar.gz sower sower.toml
GOOS=linux GOARCH=mips make build
tar czvf sower-linux-mips.tar.gz sower sower.toml
GOOS=linux GOARCH=mipsle make build
tar czvf sower-linux-mipsle.tar.gz sower sower.toml
GOOS=darwin GOARCH=amd64 make build
tar czvf sower-darwin-amd64.tar.gz sower sower.toml
GOOS=windows GOARCH=amd64 make build
tar czvf sower-windows-amd64.tar.gz sower.exe sower.toml
GOOS=windows GOARCH=386 make build
tar czvf sower-windows-386.tar.gz sower.exe sower.toml
- name: Release
uses: softprops/action-gh-release@master
with:
files: |
sower-linux-amd64.tar.gz
sower-linux-386.tar.gz
sower-linux-arm.tar.gz
sower-linux-arm64.tar.gz
sower-linux-mips.tar.gz
sower-linux-mipsle.tar.gz
sower-darwin-amd64.tar.gz
sower-windows-amd64.tar.gz
sower-windows-386.tar.gz
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+5 -5
View File
@@ -12,11 +12,11 @@
*.out
.DS_Store
**/.DS_Store
._*
*.swp
*.swo
.vscode
.idea
sower
cert.pem
**/cert.pem
privkey.pem
**/privkey.pem
sower.exe*
/sower.toml
+9 -19
View File
@@ -1,22 +1,12 @@
SERVER:=127.0.0.1:5533
default: build
generate:
go generate ./...
default: test build
test:
go vet ./...
go list ./... | grep -v internal | xargs go test
build:
GOBIN=$(PWD) go install -v
go build -ldflags "-w -s \
-X conf.version=$(shell git describe --tags --always) \
-X conf.date=$(shell date +%Y-%m-%d)"
image:
docker build -t sower -f .github/Dockerfile .
kill:
sudo pkill -9 sower || true
client: build kill
sudo $(PWD)/sower -f conf/sower.toml -logtostderr
server: build kill
$(PWD)/sower -n QUIC -logtostderr -v 1
run: build kill
$(PWD)/sower -n QUIC -logtostderr -v 1 &
sudo $(PWD)/sower -f conf/sower.toml -logtostderr
+84 -42
View File
@@ -1,48 +1,90 @@
# sower
Yet another cross platform transparent proxy tool
[![GitHub release](http://img.shields.io/github/release/wweir/sower.svg?style=popout)](https://github.com/wweir/sower/releases)
[![Actions Status](https://github.com/wweir/sower/workflows/Go/badge.svg)](https://github.com/wweir/sower/actions)
[![Docker Cloud Build Status](https://img.shields.io/docker/cloud/build/wweir/sower.svg?style=popout)](https://hub.docker.com/r/wweir/sower)
[![GitHub issue](https://img.shields.io/github/issues/wweir/sower.svg?style=popout)](https://github.com/wweir/sower/issues)
[![GitHub star](https://img.shields.io/github/stars/wweir/sower.svg?style=popout)](https://github.com/wweir/sower/stargazers)
[![GitHub license](https://img.shields.io/github/license/wweir/sower.svg?style=popout)](LICENSE)
## architecture
```
request target servers
<-------------+ +------------->
| |
| |
+------------server-------------+
| | relay service| |
| +-----+---------------------+ |
| | | |
| | parsr http(s) target url | |
| | | |
| +---------------------------+ |
| |
+--------^----------------------+
| request domain server
quic connection +---------->
| |
+--------+---client+------+-----+
| | |
| | |
| relay service | dns |
| | server |
| | |
| 127.0.0.1 or other |
| | |
+-------^----------+---^----^---+
| | |
| | | +----->
| +----------+ | |
2 1 1 2
+ + + +
block request normal request
中文介绍见 [Wiki](https://github.com/wweir/sower/wiki)
The sower is a cross-platform intelligent transparent proxy tool.
The first time you visit a new website, the sower will detect if the domain is accessible and add it in the dynamic detect list. So, you do not need to care about the rules, sower will handle it intelligently.
Sower provider both http_proxy/https_proxy and DNS-based proxy. All these kinds of proxy support intelligent router. You can also port-forward any TCP request to remote, such as SSH / SMTP / POP3.
You can enjoy it by setting http_proxy or your DNS without any other settings.
If you already have another proxy solution, you can use it's socks5(h) service as a parent proxy to enjoy the sower's intelligent router.
## Installation
To enjoy the sower, you need to deploy sower on both server-side and client-side.
The installation script has been integrated into the sower. You can install sower as system service by running `./sower -install 'xxx'` in terminal on the three most popular operating system platforms: Linux / Windows and masOS.
## Server
*If you already have another proxy solution with socks5h support, you can skip server-side.*
At the server-side, the sower runs just like a web server proxy.
It redirects HTTP requests to HTTPS and proxy https requests to the upstream HTTP service.
You can use your certificate or use the auto-generated certificate by the sower.
What you must set is the upstream HTTP service. You can set it by parameter `-s`, eg:
``` shell
# sower -s 127.0.0.1:8080
```
## install
1. install server on `server node` by `make server`
2. write config file, example: [conf/sower.toml](https://github.com/wweir/sower/blob/master/conf/sower.toml)
3. install client on `client node` by `make client`
4. add `127.0.0.1` as you first domain server
## Client
The easiest way to run it is:
``` shell
# sower -c aa.bb.cc # the `aa.bb.cc` can also be `socks5h://127.0.0.1:1080`
```
But a configuration file is recommended to persist dynamic rules in client side.
## todo
- [ ] authenticate
- [ ] broker
There are 3 kinds of proxy solutions, they are HTTP(S)_PROXY / DNS-based proxy / port-forward.
### HTTP(S)_PROXY
An HTTP(S)_PROXY listening on `:8080` is set by default if you run sower as client mode.
### DNS-based proxy
**DNS-based** solution is not recommanded now, for incompatible with TLS 1.3 esni extention. DNS flush logic has been dropped, you should flush your dns cache manually
in Windows and macOS.
You can set the `dns_serve_ip` field in the configuration file to start the DNS-based proxy. You should also set the value of `dns_upstream` as your default DNS in OS.
If you want to enjoy the full experience provided by the sower, you can take sower as your private DNS on a long-running server and set it as your default DNS in your router.
### port-forward
The port-forward can be only setted in configuration file, you can set it in section `client.port_mapping`, eg:
``` toml
[client.port_mapping]
":2222"="aa.bb.cc:22"
```
## Architecture
```
relay <--+ +-> target
http service | | service
+-------+-------+----+
| sower server |
+----^-------^-------+
80 443
301 http -+ +----- https
to https | service
protected
by tls
socks5 |
dns <---+ ^ | +--> direct
relay | | | | request
+---+---+----+--+----+
| sower client |
+----^--^----^---^---+
| | | |
dns --+ + + +-- port
80 http(s) forward
443 proxy
```
+118 -76
View File
@@ -1,108 +1,150 @@
package conf
import (
"context"
"flag"
"net"
"os/exec"
"os"
"path/filepath"
"strconv"
"time"
"sync"
"github.com/BurntSushi/toml"
"github.com/golang/glog"
"github.com/wweir/fsnotify"
toml "github.com/pelletier/go-toml"
"github.com/wweir/sower/util"
"github.com/wweir/utils/log"
)
var Conf = struct {
ConfigFile string
NetType string `toml:"net_type"`
Password string `toml:"password"`
type Client struct {
Address string `toml:"address"`
HTTPProxy string `toml:"http_proxy"`
DNSServeIP string `toml:"dns_serve_ip"`
DNSUpstream string `toml:"dns_upstream"`
PortForward map[string]string `toml:"port_forward"`
ServerPort string `toml:"server_port"`
ServerAddr string `toml:"server_addr"`
Router struct {
DetectLevel int `toml:"detect_level"`
ProxyList []string `toml:"proxy_list"`
DirectList []string `toml:"direct_list"`
} `toml:"router"`
}
type Server struct {
Upstream string `toml:"upstream"`
CertFile string `toml:"cert_file"`
KeyFile string `toml:"key_file"`
CertEmail string `toml:"cert_email"`
}
DnsServer string `toml:"dns_server"`
ClientIP string `toml:"client_ip"`
ClientIPNet net.IP `toml:"-"`
ClearDnsCache string `toml:"clear_dns_cache"`
var (
version, date string
BlockList []string `toml:"blocklist"`
Suggestions []string `toml:"suggestions"`
Verbose int `toml:"verbose"`
}{}
execFile, _ = os.Executable()
execDir, _ = filepath.Abs(filepath.Dir(execFile))
// Conf full config, include common and server / client
conf = struct {
file string
Password string `toml:"password"`
Client Client `toml:"client"`
Server Server `toml:"server"`
}{}
)
func init() {
flag.StringVar(&Conf.ConfigFile, "f", "", "config file location")
flag.StringVar(&Conf.NetType, "n", "QUIC", "proxy net type (QUIC|KCP|TCP)")
flag.StringVar(&Conf.Password, "p", "12345678", "password")
flag.StringVar(&Conf.ServerPort, "P", "5533", "server mode listen port")
flag.StringVar(&Conf.ServerAddr, "s", "", "server IP (run in client mode if set)")
flag.StringVar(&Conf.DnsServer, "d", "114.114.114.114", "client dns server")
flag.StringVar(&Conf.ClientIP, "c", "127.0.0.1", "client dns service redirect IP")
func Init() (*Client, *Server, string) {
beforeInitFlag()
flag.StringVar(&conf.Password, "password", "", "password")
flag.StringVar(&conf.Server.Upstream, "s", "", "upstream http service, eg: 127.0.0.1:8080")
flag.StringVar(&conf.Server.CertFile, "s_cert", "", "tls cert file, gen cert from letsencrypt if empty")
flag.StringVar(&conf.Server.KeyFile, "s_key", "", "tls key file, gen cert from letsencrypt if empty")
flag.StringVar(&conf.Client.Address, "c", "", "remote server domain, eg: aa.bb.cc, socks5h://127.0.0.1:1080")
flag.StringVar(&conf.Client.HTTPProxy, "http_proxy", ":8080", "http proxy, empty to disable")
flag.IntVar(&conf.Client.Router.DetectLevel, "level", 0, "dynamic rule detect level: -4~4")
if !flag.Parsed() {
flag.Parse()
}
afterInitFlag()
if Conf.ConfigFile == "" {
return
defer log.Infow("starting", "config", &conf)
if conf.file == "" {
return &conf.Client, &conf.Server, conf.Password
}
if err := OnRefreash[0](); err != nil {
panic(err)
}
watchConfigFile()
}
var OnRefreash = []func() error{func() error {
if _, err := toml.DecodeFile(Conf.ConfigFile, &Conf); err != nil {
return err
}
Conf.ClientIPNet = net.ParseIP(Conf.ClientIP)
// clear dns cache
if Conf.ClearDnsCache != "" {
ctx, _ := context.WithTimeout(context.TODO(), 5*time.Second)
if err := exec.CommandContext(ctx, "sh", "-c", Conf.ClearDnsCache).Run(); err != nil {
glog.Errorln(err)
for i := range loadConfigFns {
if err := loadConfigFns[i].fn(); err != nil {
log.Fatalw("load config", "config", conf.file, "step", loadConfigFns[i].step, "err", err)
}
}
return &conf.Client, &conf.Server, conf.Password
}
// for glog
if err := flag.Set("v", strconv.Itoa(Conf.Verbose)); err != nil {
// refreshFns will be executed while init and write new config
var loadConfigFns = []struct {
step string
fn func() error
}{{"load_config", func() error {
f, err := os.OpenFile(conf.file, os.O_RDONLY, 0644)
if err != nil {
return err
}
return nil
}}
defer f.Close()
func watchConfigFile() {
watcher, err := fsnotify.NewWatcher()
if err != nil {
glog.Fatalln(err)
}
if err := watcher.Add(filepath.Dir(Conf.ConfigFile)); err != nil {
glog.Fatalln(err)
return toml.NewDecoder(f).Decode(&conf)
}}}
// flushCh to avoid parallel persist
var flushCh = make(chan struct{})
var flushOnce = sync.Once{}
// PersistRule persist rule into config file
func PersistRule(domain string) {
flushOnce.Do(func() {
go flushConfDaemon()
})
log.Infow("persist direct rule into config", "domain", domain)
conf.Client.Router.DirectList = append(conf.Client.Router.DirectList, domain)
select {
case flushCh <- struct{}{}:
default:
}
}
func flushConfDaemon() {
for range flushCh {
// safe write file
if conf.file != "" {
f, err := os.OpenFile(conf.file+"~", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
log.Errorw("flush config", "step", "flush", "err", err)
continue
}
go func() {
for {
select {
case event := <-watcher.Events:
if event.Op == fsnotify.Rename {
if err := watcher.Add(Conf.ConfigFile); err != nil {
glog.Errorln(err)
}
}
conf.Client.Router.DirectList =
util.NewReverseSecSlice(conf.Client.Router.DirectList).Sort().Uniq()
glog.Infof("watch %s event: %v", Conf.ConfigFile, event)
for i := range OnRefreash {
if err := OnRefreash[i](); err != nil {
glog.Errorln(err)
}
if err := toml.NewEncoder(f).ArraysWithOneElementPerLine(true).Encode(&conf); err != nil {
log.Errorw("flush config", "step", "flush", "err", err)
f.Close()
continue
}
f.Close()
if stat, err := os.Stat(conf.file); err != nil {
log.Warnw("get file stat", "file", conf.file, "err", err)
} else {
// There is no common way to transfer ownership for a file
// cross-platform. Drop the ownership support but file mod.
if err = os.Chmod(conf.file+"~", stat.Mode()); err != nil {
log.Warnw("set file mod", "file", conf.file+"~", "err", err)
}
case err := <-watcher.Errors:
glog.Fatalln(err)
}
if err = os.Rename(conf.file+"~", conf.file); err != nil {
log.Errorw("flush config", "step", "flush", "err", err)
continue
}
}
}()
// reload config
for i := range loadConfigFns {
if err := loadConfigFns[i].fn(); err != nil {
log.Errorw("flush config", "step", loadConfigFns[i].step, "err", err)
}
}
}
}
+100
View File
@@ -0,0 +1,100 @@
// +build darwin
package conf
import (
"context"
"flag"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"time"
"github.com/wweir/utils/log"
)
const svcPath = "/Library/LaunchDaemons/sower.plist"
const svcFile = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>sower</string>
<key>ProgramArguments</key>
<array>
<string>/bin/sh</string>
<string>-c</string>
<string>%s %s</string>
</array>
<key>KeepAlive</key>
<true/>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>`
var (
ConfigDir = ""
installCmd = ""
uninstallFlag = false
)
func beforeInitFlag() {
if _, err := os.Stat(execDir + "/sower.toml"); err == nil {
ConfigDir = execDir
} else {
dir, _ := os.UserConfigDir()
ConfigDir = filepath.Join("/", dir, "sower")
}
if _, err := os.Stat(ConfigDir + "/sower.toml"); err != nil {
flag.StringVar(&conf.file, "f", "", "config file, rewrite all other parameters if set")
} else {
flag.StringVar(&conf.file, "f", ConfigDir+"/sower.toml", "config file, rewrite all other parameters if set")
}
flag.StringVar(&installCmd, "install", "", "install service with cmd, eg: '-f \""+ConfigDir+"/sower.toml\"'")
flag.BoolVar(&uninstallFlag, "uninstall", false, "uninstall service")
}
func afterInitFlag() {
switch {
case installCmd != "":
install()
case uninstallFlag:
uninstall()
default:
return
}
os.Exit(0)
}
func install() {
if err := ioutil.WriteFile(svcPath, []byte(fmt.Sprintf(svcFile, execFile, installCmd)), 0644); err != nil {
log.Fatalw("write service file", "err", err)
}
execute("launchctl unload " + svcPath)
if err := execute("launchctl load -wF " + svcPath); err != nil {
log.Fatalw("install service", "err", err)
}
}
func uninstall() {
execute("launchctl unload " + svcPath)
os.Remove(svcPath)
os.RemoveAll("/etc/sower")
}
func execute(cmd string) error {
ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
defer cancel()
out, err := exec.CommandContext(ctx, "sh", "-c", cmd).CombinedOutput()
if err != nil {
return fmt.Errorf("cmd: %s, err: %s, output: %s", cmd, err, out)
}
return nil
}
+105
View File
@@ -0,0 +1,105 @@
// +build linux
package conf
import (
"context"
"flag"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"time"
"github.com/wweir/utils/log"
)
const svcPath = "/etc/systemd/system/sower.service"
const svcFile = `[Unit]
Description=Sower client service
After=network.target
[Install]
WantedBy=multi-user.target
[Service]
Type=simple
User=root
WorkingDirectory=/tmp
ExecStart=%s %s
RestartSec=3
Restart=on-failure`
var (
ConfigDir = ""
installCmd = ""
uninstallFlag = false
)
func beforeInitFlag() {
if _, err := os.Stat(execDir + "/sower.toml"); err == nil {
ConfigDir = execDir
} else if stat, err := os.Stat("/etc/sower"); err == nil && stat.IsDir() {
ConfigDir = "/etc/sower"
} else {
dir, _ := os.UserConfigDir()
ConfigDir = filepath.Join("/", dir, "sower")
}
if _, err := os.Stat(ConfigDir + "/sower.toml"); err != nil {
flag.StringVar(&conf.file, "f", "", "config file, rewrite all other parameters if set")
} else {
flag.StringVar(&conf.file, "f", ConfigDir+"/sower.toml", "config file, rewrite all other parameters if set")
}
flag.StringVar(&installCmd, "install", "", "install service with cmd, eg: '-f "+ConfigDir+"/sower.toml'")
flag.BoolVar(&uninstallFlag, "uninstall", false, "uninstall service")
}
func afterInitFlag() {
switch {
case installCmd != "":
install()
case uninstallFlag:
uninstall()
default:
return
}
os.Exit(0)
}
func install() {
if err := ioutil.WriteFile(svcPath, []byte(fmt.Sprintf(svcFile, execFile, installCmd)), 0644); err != nil {
log.Fatalw("write service file", "err", err)
}
if err := execute("systemctl daemon-reload"); err != nil {
log.Fatalw("install service", "err", err)
}
if err := execute("systemctl enable sower"); err != nil {
log.Fatalw("install service", "err", err)
}
if err := execute("systemctl start sower"); err != nil {
log.Fatalw("install service", "err", err)
}
}
func uninstall() {
execute("systemctl stop sower")
execute("systemctl disable sower")
os.Remove(svcPath)
os.RemoveAll("/etc/sower")
}
func execute(cmd string) error {
ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
defer cancel()
out, err := exec.CommandContext(ctx, "sh", "-c", cmd).CombinedOutput()
if err != nil {
return fmt.Errorf("cmd: %s, err: %s, output: %s", cmd, err, out)
}
return nil
}
+188
View File
@@ -0,0 +1,188 @@
// +build windows
package conf
import (
"context"
"flag"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"syscall"
"time"
"github.com/wweir/utils/log"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/svc"
"golang.org/x/sys/windows/svc/eventlog"
"golang.org/x/sys/windows/svc/mgr"
)
const name = "sower"
const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown | svc.AcceptPauseAndContinue
var (
ConfigDir = ""
installCmd = false
uninstallFlag = false
)
func beforeInitFlag() {
flag.StringVar(&conf.file, "f", filepath.Join(execDir, "sower.toml"), "config file, rewrite all other parameters if set")
flag.BoolVar(&installCmd, "install", false, "put any character to install as a service, eg: true")
}
func afterInitFlag() {
switch {
case installCmd:
install()
case uninstallFlag:
uninstall()
default:
runAsService()
return
}
os.Exit(0)
}
func runAsService() {
os.Chdir(filepath.Dir(os.Args[0]))
if active, err := svc.IsAnInteractiveSession(); err != nil {
log.Fatalf("failed to determine if we are running in an interactive session: %v", err)
} else if !active {
go func() {
elog, err := eventlog.Open(name)
if err != nil {
log.Fatalw("install service", "err", err)
}
defer elog.Close()
if err := svc.Run(name, &myservice{}); err != nil {
elog.Error(1, fmt.Sprintf("%s service failed: %v", name, err))
log.Fatalw("install service", "err", err)
}
elog.Info(1, fmt.Sprintf("winsvc.RunAsService: %s service stopped", name))
os.Exit(0)
}()
}
}
func install() {
mgrDo(func(m *mgr.Mgr) error {
s, err := m.OpenService(name)
if err == nil {
s.Close()
return fmt.Errorf("service %s already exists", name)
}
s, err = m.CreateService(name, execFile, mgr.Config{
DisplayName: "Sower Proxy",
StartType: windows.SERVICE_AUTO_START,
})
if err != nil {
return err
}
defer s.Close()
err = eventlog.InstallAsEventCreate(name, eventlog.Error|eventlog.Warning|eventlog.Info)
if err != nil {
s.Delete()
return fmt.Errorf("SetupEventLogSource() failed: %s", err)
}
return s.Start()
})
}
func uninstall() {
serviceDo(func(s *mgr.Service) error {
err := s.Delete()
if err != nil {
return err
}
return eventlog.Remove(name)
})
}
func serviceDo(fn func(*mgr.Service) error) {
mgrDo(func(m *mgr.Mgr) error {
s, err := m.OpenService(name)
if err != nil {
return fmt.Errorf("could not access service: %v", err)
}
defer s.Close()
return fn(s)
})
}
func mgrDo(fn func(m *mgr.Mgr) error) {
m, err := mgr.Connect()
if err != nil {
log.Fatalw("install service", "err", err)
}
defer m.Disconnect()
if err := fn(m); err != nil {
log.Fatalw("install service", "err", err)
}
}
type myservice struct{}
func (m *myservice) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) {
elog, err := eventlog.Open(name)
if err != nil {
log.Errorw("install service", "err", err)
return
}
defer elog.Close()
elog.Info(1, strings.Join(args, "-"))
changes <- svc.Status{State: svc.StartPending}
changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
for {
c := <-r
switch c.Cmd {
case svc.Interrogate:
changes <- c.CurrentStatus
// Testing deadlock from https://code.google.com/p/winsvc/issues/detail?id=4
time.Sleep(100 * time.Millisecond)
changes <- c.CurrentStatus
case svc.Stop, svc.Shutdown:
changes <- svc.Status{State: svc.StopPending}
return
case svc.Pause:
changes <- svc.Status{State: svc.Paused, Accepts: cmdsAccepted}
case svc.Continue:
changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
default:
elog.Error(1, fmt.Sprintf("unexpected control request #%d", c))
}
}
}
func execute(cmd string) error {
ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
defer cancel()
var cmds []string
for _, cmd := range strings.Split(cmd, " ") {
if cmd == "" {
continue
}
if strings.HasPrefix(cmd, "/") {
cmd = strings.Replace(cmd, "/", "-", 1)
}
cmds = append(cmds, cmd)
}
if len(cmds) != 0 {
return nil
}
command := exec.CommandContext(ctx, cmds[0], cmds[1:]...)
command.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
if out, err := command.CombinedOutput(); err != nil {
return fmt.Errorf("cmd: %s, output: %s, err: %w", cmd, out, err)
}
return nil
}
+54 -83
View File
@@ -1,83 +1,54 @@
net_type="KCP"
password="12345678"
server_port="5533"
server_addr="remote-server:5533" # replce it to remote server
dns_server="114.114.114.114"
# client_ip="192.168.0.1"
# clear_dns_cache="sudo killall -HUP mDNSResponder"
blocklist=[
"*.google.com", # google
"ocsp.pki.goog",
"g.co",
"*.goo.gl",
"*.googleusercontent.com",
"*.googleapis.com",
"gmail.com",
"*.youtube.com", # youtube
"*.ytimg.com",
"*.ggpht.com",
"*.googlevideo.com",
"*.vimeo.com",
"*.facebook.com", # facebook
"*.xx.fbcdn.net",
"connect.facebook.net",
"*.twitter.com", # twitter
"t.co",
"*.twimg.com",
"www.linkedin.com",
"www.reddit.com", # reddit
"www.redditmedia.com",
"*.blogspot.com", # blogspot
"www.blogger.com",
"www.feedburner.com",
"img1.blogblog.com",
"*.aws.amazon.com", # amazon
"m.media-amazon.com",
"*.awsstatic.com",
"*.s3.amazonaws.com",
"*.cloudfront.net", # atlassian
"synchrony-cdn.atlassian.com",
"avatar-cdn.atlassian.com",
"*.medium.com",
"accounts-static.cdn.mozilla.net", # firefox
"*.services.mozilla.com",
"pocket-image-cache.com", # pocket
"*.golang.org", # golang
"go.googlesource.com",
"godoc.org",
"gist.github.com", # github
"*.k8s.io", # k8s
"k8s.gcr.io",
"*.wikipedia.org", # wikipeida
"*.wikimedia.org",
"*.wikisource.org",
"lookup-api.apple.com",
"lookup-api.apple.com.edgekey.net",
"e16991.b.akamaiedge.net",
"*.web.telegram.org", # telegram
"core.telegram.org",
"telegram.org",
"telegram.me",
"t.me",
"*.slack.com", #slack
"*.slack-edge.com",
"js.driftt.com",
"cloud.feedly.com", # feedly
"static.grammarly.com", # grammarly
"apkpure.com", # apkpure
"download.apkpure.com",
"*.nytimes.com", # New York times
"*.nyt.com",
"bandwagonhost.com", # bandwagonhost
"www.bwh1.net",
"*.akadns.net", # suggestions
"*.haxx.se",
"shadowsocks.org",
"t66y.com",
"ocsp.godaddy.com",
"*.qualtrics.com",
"*.starfieldtech.com",
"*.typekit.net",
"www.v2ray.com",
]
verbose=0
password="" # sower password
[client]
address = "" # aa.bb.cc, socks5h://127.0.0.1:1080
http_proxy=":8080"
dns_serve_ip="" # eg: 127.0.0.1. keep empty to disable DNS solution
dns_upstream="" # keep empty to set via dhcp, not effective in any environment
[client.port_forward]
# eg: ":2222"="aa.bb.cc:22"
[client.router]
detect_level = 0 # [-4, 4], the bigger the harder to add
direct_list = [
"**.in-addr.arpa",
"imap.*.*",
"imap.*.*.*",
"smtp.*.*",
"smtp.*.*.*",
"pop.*.*",
"pop.*.*.*",
"**.cn",
]
proxy_list = [
"**.google.*",
"**.goo.gl",
"**.googleusercontent.com",
"**.googleapis.com",
"*.googlesource.com",
"**.youtube.com",
"**.ytimg.com",
"**.ggpht.com",
"**.googlevideo.com",
"**.facebook.com",
"**.fbcdn.net",
"**.twitter.com",
"**.twimg.com",
"**.blogspot.com",
"**.appspot.com",
"**.wikipedia.org",
"*.cloudfront.net",
"**.amazon.com",
"**.amazonaws.com",
"*.githubusercontent.com",
"*.githubassets.com",
"*.github.*",
]
[server]
cert_email = "" # eg: user@aa.bb.cc
cert_file = "" # eg: /etc/ssl/server.crt
key_file = "" # eg: /etc/ssl/server.key
upstream = "" # eg: 127.0.0.1:8080
+59
View File
@@ -0,0 +1,59 @@
package dhcp
import (
"math/rand"
"net"
"runtime"
"time"
"github.com/krolaw/dhcp4"
"github.com/libp2p/go-reuseport"
"github.com/pkg/errors"
)
var xid = make([]byte, 4)
var broadcastAddr, _ = net.ResolveUDPAddr("udp", "255.255.255.255:67")
func GetDefaultDNSServer() (string, error) {
iface, err := PickInternetInterface()
if err != nil {
return "", errors.Wrap(err, "pick interface")
}
rand.Read(xid)
pack := dhcp4.RequestPacket(dhcp4.Discover, iface.HardwareAddr, net.IPv4(0, 0, 0, 0), xid, true, []dhcp4.Option{
{Code: dhcp4.OptionRequestedIPAddress, Value: []byte(iface.IP.To4())},
{Code: dhcp4.End},
})
var conn net.PacketConn
if runtime.GOOS == "windows" {
if conn, err = reuseport.ListenPacket("udp4", iface.IP.String()+":68"); err != nil {
return "", errors.Wrap(err, "listen dhcp")
}
} else {
if conn, err = reuseport.ListenPacket("udp4", "0.0.0.0:68"); err != nil {
return "", errors.Wrap(err, "listen dhcp")
}
}
defer conn.Close()
conn.SetDeadline(time.Now().Add(3 * time.Second))
if _, err := conn.WriteTo([]byte(pack), broadcastAddr); err != nil {
return "", errors.Wrap(err, "write broadcast")
}
buf := make([]byte, 1500 /*MTU*/)
n, _, err := conn.ReadFrom(buf)
if err != nil {
return "", errors.Wrap(err, "read dhcp offer")
}
pack = dhcp4.Packet(buf[:n])
dnsBytes := pack.ParseOptions()[dhcp4.OptionDomainNameServer]
if len(dnsBytes) < 4 {
return "", errors.New("no DNS setting in upstream network device")
}
return net.IPv4(dnsBytes[0], dnsBytes[1], dnsBytes[2], dnsBytes[3]).String(), nil
}
+15
View File
@@ -0,0 +1,15 @@
package dhcp_test
import (
"fmt"
"github.com/wweir/sower/dhcp"
)
func Example_dns() {
got, err := dhcp.GetDefaultDNSServer()
if err != nil {
panic(err)
}
fmt.Println(got)
}
+30
View File
@@ -0,0 +1,30 @@
// +build !windows
package dhcp
import (
"errors"
"net"
)
// PickInternetInterface pick the first active net interface
func PickInternetInterface() (*Iface, error) {
ifaces, err := net.Interfaces()
if err != nil {
return nil, err
}
for i := range ifaces {
if len(ifaces[i].HardwareAddr) == 0 {
continue
}
addrs, _ := ifaces[i].Addrs()
for _, addr := range addrs {
if ip := addr.(*net.IPNet).IP.To4(); ip != nil {
return &Iface{ifaces[i].HardwareAddr, ip}, nil
}
}
}
return nil, errors.New("no valid interface")
}
+15
View File
@@ -0,0 +1,15 @@
package dhcp_test
import (
"fmt"
"github.com/wweir/sower/dhcp"
)
func Example_iface() {
got, err := dhcp.PickInternetInterface()
if err != nil {
panic(err)
}
fmt.Println(got)
}
+51
View File
@@ -0,0 +1,51 @@
// +build windows
package dhcp
import (
"bytes"
"errors"
"net"
"os"
"syscall"
"unsafe"
)
// PickInternetInterface pick the first active net interface
func PickInternetInterface() (*Iface, error) {
list, err := getAdapterList()
if err != nil {
return nil, err
}
for {
if list.DhcpEnabled == 1 && list.LeaseObtained != 0 {
ip := bytes.TrimRight(list.IpAddressList.IpAddress.String[:], string([]byte{0}))
return &Iface{net.HardwareAddr(list.Address[:list.AddressLength]), net.ParseIP(string(ip))}, nil
}
if list.Next != nil {
list = list.Next
} else {
return nil, errors.New("no valid interface")
}
}
}
func getAdapterList() (*syscall.IpAdapterInfo, error) {
b := make([]byte, 1000)
l := uint32(len(b))
a := (*syscall.IpAdapterInfo)(unsafe.Pointer(&b[0]))
// TODO(mikio): GetAdaptersInfo returns IP_ADAPTER_INFO that
// contains IPv4 address list only. We should use another API
// for fetching IPv6 stuff from the kernel.
err := syscall.GetAdaptersInfo(a, &l)
if err == syscall.ERROR_BUFFER_OVERFLOW {
b = make([]byte, l)
a = (*syscall.IpAdapterInfo)(unsafe.Pointer(&b[0]))
err = syscall.GetAdaptersInfo(a, &l)
}
if err != nil {
return nil, os.NewSyscallError("GetAdaptersInfo", err)
}
return a, nil
}
+9
View File
@@ -0,0 +1,9 @@
package dhcp
import "net"
// Iface is net interface address info
type Iface struct {
net.HardwareAddr
net.IP
}
-111
View File
@@ -1,111 +0,0 @@
package dns
import (
"net"
"strings"
"time"
"github.com/golang/glog"
"github.com/miekg/dns"
"github.com/wweir/sower/conf"
)
const colon = byte(':')
func StartDNS(dnsServer string) {
dns.HandleFunc(".", func(w dns.ResponseWriter, r *dns.Msg) {
// *Msg r has an TSIG record and it was validated
if r.IsTsig() != nil && w.TsigStatus() == nil {
r.SetTsig(r.Extra[len(r.Extra)-1].(*dns.TSIG).Hdr.Name, dns.HmacMD5, 300, time.Now().Unix())
}
//https://stackoverflow.com/questions/4082081/requesting-a-and-aaaa-records-in-single-dns-query/4083071#4083071
if len(r.Question) == 0 {
return
}
domain := r.Question[0].Name
if idx := strings.IndexByte(domain, colon); idx > 0 {
domain = domain[:idx]
}
if len(conf.Conf.BlockList) == 0 {
bestTry(w, r, domain, dnsServer)
} else {
manual(w, r, domain, dnsServer)
}
})
server := &dns.Server{Addr: ":53", Net: "udp"}
glog.Fatalln(server.ListenAndServe())
}
func bestTry(w dns.ResponseWriter, r *dns.Msg, domain, dnsServer string) {
msg, _ := dns.Exchange(r, dnsServer+":53")
if msg == nil {
return
}
if len(msg.Answer) == 0 { // expose any response
w.WriteMsg(msg)
return
}
var ip string
switch msg.Answer[0].(type) {
case *dns.A:
ip = msg.Answer[0].(*dns.A).A.String()
case *dns.AAAA:
ip = "[" + msg.Answer[0].(*dns.AAAA).AAAA.String() + "]"
default:
w.WriteMsg(msg)
return
}
if _, err := net.DialTimeout("tcp", ip+":http", time.Second); err != nil {
glog.V(2).Infoln(ip+":80", err)
w.WriteMsg(localA(r, domain))
return
}
w.WriteMsg(msg)
}
func manual(w dns.ResponseWriter, r *dns.Msg, domain, dnsServer string) {
if rule.Match(strings.TrimSuffix(domain, ".")) {
glog.V(2).Infof("match %s suss", domain)
w.WriteMsg(localA(r, domain))
return
}
msg, err := dns.Exchange(r, dnsServer+":53")
if msg == nil { // expose any response except nil
glog.V(1).Infof("get dns of %s fail: %s", domain, err)
return
}
w.WriteMsg(msg)
if conf.Conf.Verbose != 0 && len(msg.Answer) != 0 {
go func() {
_, err := net.DialTimeout("tcp", domain+":http", 2*time.Second)
if err == nil || !strings.Contains(err.Error(), "timeout") {
return
}
_, err = net.DialTimeout("tcp", domain+":https", 3*time.Second)
if err == nil || !strings.Contains(err.Error(), "timeout") {
return
}
glog.V(1).Infof("SUGGEST check (%s) http(s) service: %s", domain, err)
}()
}
}
func localA(r *dns.Msg, domain string) *dns.Msg {
m := new(dns.Msg)
m.SetReply(r)
m.Answer = []dns.RR{&dns.A{
Hdr: dns.RR_Header{Name: domain, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 20},
A: conf.Conf.ClientIPNet,
}}
return m
}
-73
View File
@@ -1,73 +0,0 @@
package dns
import (
"strings"
)
type Node struct {
Node map[string]*Node
}
func NewNode() *Node {
return &Node{Node: map[string]*Node{}}
}
func NewNodeFromRule(rules ...string) *Node {
node := NewNode()
for i := range rules {
node.Add(strings.Split(rules[i], "."))
}
return node
}
func (n *Node) String() string {
return n.string("")
}
func (n *Node) string(prefix string) (out string) {
for key, val := range n.Node {
out += prefix + key + "\n" + val.string(prefix+" ")
}
return
}
func (n *Node) Add(secs []string) {
length := len(secs)
switch length {
case 0:
return
case 1:
n.Node[secs[length-1]] = NewNode()
default:
subNode, ok := n.Node[secs[length-1]]
if !ok {
subNode = NewNode()
n.Node[secs[length-1]] = subNode
}
subNode.Add(secs[:length-1])
}
}
func (n *Node) Match(addr string) bool {
return n.matchSecs(strings.Split(addr, "."))
}
func (n *Node) matchSecs(secs []string) bool {
length := len(secs)
if length == 0 {
switch len(n.Node) {
case 0:
return true
case 1:
_, ok := n.Node["*"]
return ok
default:
return false
}
}
if n, ok := n.Node[secs[length-1]]; ok {
return n.matchSecs(secs[:length-1])
}
_, ok := n.Node["*"]
return ok
}
-19
View File
@@ -1,19 +0,0 @@
package dns
import (
"github.com/golang/glog"
"github.com/wweir/sower/conf"
)
var rule *Node
func init() {
rule = NewNodeFromRule(conf.Conf.BlockList...)
glog.V(2).Infof("block rule:\n%s", rule)
conf.OnRefreash = append(conf.OnRefreash, func() error {
rule = NewNodeFromRule(conf.Conf.BlockList...)
glog.V(2).Infof("block rule:\n%s", rule)
return nil
})
}
+10 -24
View File
@@ -1,28 +1,14 @@
module github.com/wweir/sower
go 1.13
require (
github.com/BurntSushi/toml v0.3.1
github.com/bifurcation/mint v0.0.0-20181105071958-a14404e9a861 // indirect
github.com/cheekybits/genny v1.0.0 // indirect
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
github.com/golang/mock v1.1.1 // indirect
github.com/hashicorp/golang-lru v0.5.0 // indirect
github.com/klauspost/cpuid v1.2.0 // indirect
github.com/klauspost/reedsolomon v1.7.0 // indirect
github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f // indirect
github.com/lucas-clemente/quic-go v0.10.0
github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced // indirect
github.com/miekg/dns v1.1.1
github.com/onsi/ginkgo v1.7.0 // indirect
github.com/onsi/gomega v1.4.3 // indirect
github.com/pkg/errors v0.8.0
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect
github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b // indirect
github.com/tjfoc/gmsm v1.0.1 // indirect
github.com/wweir/fsnotify v1.4.8
github.com/xtaci/kcp-go v4.3.2+incompatible
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 // indirect
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc // indirect
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f // indirect
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35 // indirect
github.com/krolaw/dhcp4 v0.0.0-20190909130307-a50d88189771
github.com/libp2p/go-reuseport v0.0.1
github.com/miekg/dns v1.1.27
github.com/pelletier/go-toml v1.6.0
github.com/pkg/errors v0.9.1
github.com/wweir/utils v0.0.0-20200214114658-f6f356a08736
golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4
)
+98 -64
View File
@@ -1,68 +1,102 @@
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/bifurcation/mint v0.0.0-20181105071958-a14404e9a861 h1:x17NvoJaphEzay72TFej4OSSsgu3xRYBLkbIwdofS/4=
github.com/bifurcation/mint v0.0.0-20181105071958-a14404e9a861/go.mod h1:zVt7zX3K/aDCk9Tj+VM7YymsX66ERvzCJzw8rFCX2JU=
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/klauspost/cpuid v1.2.0 h1:NMpwD2G9JSFOE1/TJjGSo5zG7Yb2bTe7eq1jH+irmeE=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/reedsolomon v1.7.0 h1:pLFmRKGko2ZieiTGyo9DahLCIuljyxm+Zzhz/fYEonE=
github.com/klauspost/reedsolomon v1.7.0/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4=
github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f h1:sSeNEkJrs+0F9TUau0CgWTTNEwF23HST3Eq0A+QIx+A=
github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f/go.mod h1:JpH9J1c9oX6otFSgdUHwUBUizmKlrMjxWnIAjff4m04=
github.com/lucas-clemente/quic-go v0.10.0 h1:xEF+pSHYAOcu+U10Meunf+DTtc8vhQDRqlA0BJ6hufc=
github.com/lucas-clemente/quic-go v0.10.0/go.mod h1:wuD+2XqEx8G9jtwx5ou2BEYBsE+whgQmlj0Vz/77PrY=
github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced h1:zqEC1GJZFbGZA0tRyNZqRjep92K5fujFtFsu5ZW7Aug=
github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced/go.mod h1:NCcRLrOTZbzhZvixZLlERbJtDtYsmMw8Jc4vS8Z0g58=
github.com/miekg/dns v1.1.1 h1:DVkblRdiScEnEr0LR9nTnEQqHYycjkXW9bOjd+2EL2o=
github.com/miekg/dns v1.1.1/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 h1:89CEmDvlq/F7SJEOqkIdNDGJXrQIhuIx9D2DBXjavSU=
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b h1:mnG1fcsIB1d/3vbkBak2MM0u+vhGhlQwpeimUi7QncM=
github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=
github.com/tjfoc/gmsm v1.0.1 h1:R11HlqhXkDospckjZEihx9SW/2VW0RgdwrykyWMFOQU=
github.com/tjfoc/gmsm v1.0.1/go.mod h1:XxO4hdhhrzAd+G4CjDqaOkd0hUzmtPR/d3EiBBMn/wc=
github.com/wweir/fsnotify v1.4.8 h1:zxOoqwE5p91F3rmcYLQheoSeTlIVqwi2b5T7UlpElWE=
github.com/wweir/fsnotify v1.4.8/go.mod h1:4GuxIc23DXfJI1/LhBXG5eE/wJ4CzL+kk5M7woxVprc=
github.com/xtaci/kcp-go v4.3.2+incompatible h1:vM5gqHI8p5xa3eXQH279+16fG86gCz/IRamaQvWG+a8=
github.com/xtaci/kcp-go v4.3.2+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc h1:a3CU5tJYVj92DY2LaA1kUkrsqD5/3mLDhx2NcNqyW+0=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35 h1:YAFjXN64LMvktoUZH9zgY4lGc/msGN7HQfoSuKCgaDU=
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/guregu/null v3.4.0+incompatible h1:a4mw37gBO7ypcBlTJeZGuMpSxxFTV9qFfFKgWxQSGaM=
github.com/guregu/null v3.4.0+incompatible/go.mod h1:ePGpQaN9cw0tj45IR5E5ehMvsFlLlQZAkkOXZurJ3NM=
github.com/influxdata/influxdb v1.7.9/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY=
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/krolaw/dhcp4 v0.0.0-20190909130307-a50d88189771 h1:t2c2B9g1ZVhMYduqmANSEGVD3/1WlsrEYNPtVoFlENk=
github.com/krolaw/dhcp4 v0.0.0-20190909130307-a50d88189771/go.mod h1:0AqAH3ZogsCrvrtUpvc6EtVKbc3w6xwZhkvGLuqyi3o=
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/libp2p/go-reuseport v0.0.1 h1:7PhkfH73VXfPJYKQ6JwS5I/eVcoyYi9IMNGc6FWpFLw=
github.com/libp2p/go-reuseport v0.0.1/go.mod h1:jn6RmB1ufnQwl0Q1f+YxAj8isJgDCQzaaxIFYDhcYEA=
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/miekg/dns v1.1.27 h1:aEH/kqUzUxGJ/UHcEKdJY+ugH6WEzsEBBSPa8zuy1aM=
github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4=
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/ulule/deepcopier v0.0.0-20171107155558-ca99b135e50f h1:QatZ4lsJBY3x1+Imst9g95+vUl7m52dqM9Pi4aSMW8w=
github.com/ulule/deepcopier v0.0.0-20171107155558-ca99b135e50f/go.mod h1:BNLmYJ8oMJPIPpNx5968jCyUhwEU1XT3YsuOqtbo5qo=
github.com/wweir/utils v0.0.0-20200214114658-f6f356a08736 h1:x6LiUrnHR4CCqzRzB0zqiB9hzVFcRhSKrVlZDGW0FcM=
github.com/wweir/utils v0.0.0-20200214114658-f6f356a08736/go.mod h1:Nv4eBGkUJiHDPgVowJJlQNcJYgMfst6IkjaDThH2/yI=
go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/multierr v1.3.0 h1:sFPn2GLc3poCkfrpIXGhBD2X0CMIo4Q/zSULXrj/+uc=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6 h1:Sy5bstxEqwwbYs6n0/pBuxKENqOeZUgD45Gp3Q3pqLg=
golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4 h1:sfkvUWPNGwSV+8/fNqctR5lS2AqCSqYwXdrjCxp/dXo=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425 h1:VvQyQJN0tSuecqgcIxMWnnfG5kSmgy9KZR9sW3W5QeA=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
+39 -9
View File
@@ -1,20 +1,50 @@
package main
import (
"github.com/golang/glog"
"flag"
"fmt"
"github.com/wweir/sower/conf"
"github.com/wweir/sower/dns"
"github.com/wweir/sower/proxy"
"github.com/wweir/sower/router"
"github.com/wweir/sower/transport"
)
func main() {
conf := conf.Conf
glog.Infoln("Starting:", conf)
client, server, password := conf.Init()
if conf.ServerAddr == "" {
proxy.StartServer(conf.NetType, conf.ServerPort, conf.Password)
} else {
go dns.StartDNS(conf.DnsServer)
proxy.StartClient(conf.NetType, conf.ServerAddr, conf.Password)
switch {
case server.Upstream != "":
proxy.StartServer(server.Upstream, password, conf.ConfigDir,
server.CertFile, server.KeyFile, server.CertEmail)
case client.Address != "":
route := &router.Route{
ProxyAddress: client.Address,
ProxyPassword: password,
DetectLevel: client.Router.DetectLevel,
DirectList: client.Router.DirectList,
ProxyList: client.Router.ProxyList,
PersistFn: conf.PersistRule,
}
if client.HTTPProxy != "" {
go proxy.StartHTTPProxy(client.HTTPProxy, client.Address,
[]byte(password), route.GenProxyCheck(true))
}
enableDNSSolution := client.DNSServeIP != ""
if enableDNSSolution {
transport.SetDNS(nil, client.DNSUpstream)
go proxy.StartDNS(client.DNSServeIP, client.DNSUpstream,
route.GenProxyCheck(false))
}
proxy.StartClient(client.Address, password, enableDNSSolution,
client.PortForward, route.GenProxyCheck(true))
default:
fmt.Println()
flag.Usage()
}
}
-46
View File
@@ -1,46 +0,0 @@
package parse
import (
"bufio"
"fmt"
"io"
"net"
"net/http"
"strings"
)
func ParseAddr(conn net.Conn) (teeConn *TeeConn, addr string, err error) {
teeConn = &TeeConn{Conn: conn}
teeConn.StartOrReset()
defer teeConn.Stop()
buf := make([]byte, 1)
if n, err := teeConn.Read(buf); err != nil {
return teeConn, "", fmt.Errorf("Read conn fail: %v, readed: %v", err, buf[:n])
}
teeConn.StartOrReset()
// https
if buf[0] == 0x16 { // SSL handleshake
host, _, err := extractSNI(io.Reader(teeConn))
if err != nil {
return teeConn, "", err
}
if strings.Contains(host, ":") {
return teeConn, host, nil
}
return teeConn, host + ":443", nil
}
// http
b := bufio.NewReader(teeConn)
resp, err := http.ReadRequest(b)
if err != nil {
return teeConn, "", err
}
if strings.Contains(resp.Host, ":") {
return teeConn, resp.Host, nil
}
return teeConn, resp.Host + ":80", nil
}
-232
View File
@@ -1,232 +0,0 @@
// Copyright 2016 Google Inc.
//
// 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 parse
import (
"encoding/binary"
"errors"
"fmt"
"io"
)
func extractSNI(r io.Reader) (string, int, error) {
handshake, tlsver, err := handshakeRecord(r)
if err != nil {
return "", 0, fmt.Errorf("reading TLS record: %s", err)
}
sni, err := parseHello(handshake)
if err != nil {
return "", 0, fmt.Errorf("reading ClientHello: %s", err)
}
if len(sni) == 0 {
// ClientHello did not present an SNI extension. Valid packet,
// no hostname.
return "", tlsver, nil
}
hostname, err := parseSNI(sni)
if err != nil {
return "", 0, fmt.Errorf("parsing SNI extension: %s", err)
}
return hostname, tlsver, nil
}
// Extract the indicated hostname, if any, from the given SNI
// extension bytes.
func parseSNI(b []byte) (string, error) {
b, _, err := vector(b, 2)
if err != nil {
return "", err
}
var ret []byte
for len(b) >= 3 {
typ := b[0]
ret, b, err = vector(b[1:], 2)
if err != nil {
return "", fmt.Errorf("truncated SNI extension")
}
if typ == sniHostnameID {
return string(ret), nil
}
}
if len(b) != 0 {
return "", fmt.Errorf("trailing garbage at end of SNI extension")
}
// No DNS-based SNI present.
return "", nil
}
const sniExtensionID = 0
const sniHostnameID = 0
// Parse a TLS handshake record as a ClientHello message and extract
// the SNI extension bytes, if any.
func parseHello(b []byte) ([]byte, error) {
if len(b) == 0 {
return nil, errors.New("zero length handshake record")
}
if b[0] != 1 {
return nil, fmt.Errorf("non-ClientHello handshake record type %d", b[0])
}
// We're expecting a stricter TLS parser to run after we've
// proxied, so we ignore any trailing bytes that might be present
// (e.g. another handshake message).
b, _, err := vector(b[1:], 3)
if err != nil {
return nil, fmt.Errorf("reading ClientHello: %s", err)
}
// ClientHello must be at least 34 bytes to reach the first vector
// length byte. The actual minimal size is larger than that, but
// vector() will correctly handle truncated packets.
if len(b) < 34 {
return nil, errors.New("ClientHello packet too short")
}
if b[0] != 3 {
return nil, fmt.Errorf("ClientHello has unsupported version %d.%d", b[0], b[1])
}
switch b[1] {
case 1, 2, 3:
// TLS 1.0, TLS 1.1, TLS 1.2
default:
return nil, fmt.Errorf("TLS record has unsupported version %d.%d", b[0], b[1])
}
// Skip over version and random struct
b = b[34:]
// We don't technically care about SessionID, but we care that the
// framing is well-formed all the way up to the SNI field, so that
// we are sure that we're pulling the same SNI bytes as the
// eventual TLS implementation.
vec, b, err := vector(b, 1)
if err != nil {
return nil, fmt.Errorf("reading ClientHello SessionID: %s", err)
}
if len(vec) > 32 {
return nil, fmt.Errorf("ClientHello SessionID too long (%db)", len(vec))
}
// Likewise, we're just checking the bare minimum of framing.
vec, b, err = vector(b, 2)
if err != nil {
return nil, fmt.Errorf("reading ClientHello CipherSuites: %s", err)
}
if len(vec) < 2 || len(vec)%2 != 0 {
return nil, fmt.Errorf("ClientHello CipherSuites invalid length %d", len(vec))
}
vec, b, err = vector(b, 1)
if err != nil {
return nil, fmt.Errorf("reading ClientHello CompressionMethods: %s", err)
}
if len(vec) < 1 {
return nil, fmt.Errorf("ClientHello CompressionMethods invalid length %d", len(vec))
}
// Finally, we reach the extensions.
if len(b) == 0 {
// No extensions. This is not an error, it just means we have
// no SNI payload.
return nil, nil
}
b, vec, err = vector(b, 2)
if err != nil {
return nil, fmt.Errorf("reading ClientHello extensions: %s", err)
}
if len(vec) != 0 {
return nil, fmt.Errorf("%d bytes of trailing garbage in ClientHello", len(vec))
}
for len(b) >= 4 {
typ := binary.BigEndian.Uint16(b[:2])
vec, b, err = vector(b[2:], 2)
if err != nil {
return nil, fmt.Errorf("reading ClientHello extension %d: %s", typ, err)
}
if typ == sniExtensionID {
// Found the SNI extension, return its payload. We don't
// care about anything in the packet beyond this point.
return vec, nil
}
}
if len(b) != 0 {
return nil, fmt.Errorf("%d bytes of trailing garbage in ClientHello", len(b))
}
// Successfully parsed all extensions, but there was no SNI.
return nil, nil
}
const maxTLSRecordLength = 16384
// Read one TLS record, which must be for the handshake protocol, from r.
func handshakeRecord(r io.Reader) ([]byte, int, error) {
var hdr struct {
Type uint8
Major, Minor uint8
Length uint16
}
if err := binary.Read(r, binary.BigEndian, &hdr); err != nil {
return nil, 0, fmt.Errorf("reading TLS record header: %s", err)
}
if hdr.Type != 22 {
return nil, 0, fmt.Errorf("TLS record is not a handshake")
}
if hdr.Major != 3 {
return nil, 0, fmt.Errorf("TLS record has unsupported version %d.%d", hdr.Major, hdr.Minor)
}
switch hdr.Minor {
case 1, 2, 3:
// TLS 1.0, TLS 1.1, TLS 1.2
default:
return nil, 0, fmt.Errorf("TLS record has unsupported version %d.%d", hdr.Major, hdr.Minor)
}
if hdr.Length > maxTLSRecordLength {
return nil, 0, fmt.Errorf("TLS record length is greater than %d", maxTLSRecordLength)
}
ret := make([]byte, hdr.Length)
if _, err := io.ReadFull(r, ret); err != nil {
return nil, 0, err
}
return ret, int(hdr.Minor), nil
}
func vector(b []byte, lenBytes int) ([]byte, []byte, error) {
if len(b) < lenBytes {
return nil, nil, errors.New("not enough space in packet for vector")
}
var l int
for _, b := range b[:lenBytes] {
l = (l << 8) + int(b)
}
if len(b) < l+lenBytes {
return nil, nil, errors.New("not enough space in packet for vector")
}
return b[lenBytes : l+lenBytes], b[l+lenBytes:], nil
}
-71
View File
@@ -1,71 +0,0 @@
package proxy
import (
"net"
"github.com/golang/glog"
"github.com/wweir/sower/proxy/kcp"
"github.com/wweir/sower/proxy/quic"
"github.com/wweir/sower/proxy/tcp"
"github.com/wweir/sower/shadow"
)
type Client interface {
Dial(server string) (net.Conn, error)
}
func StartClient(netType, server, password string) {
var connCh = listenLocal([]string{":80", ":443"})
var client Client
switch netType {
case QUIC.String():
client = quic.NewClient()
case KCP.String():
client = kcp.NewClient()
case TCP.String():
client = tcp.NewClient()
}
for {
conn := <-connCh
glog.V(1).Infof("new conn from (%s) to (%s)", conn.RemoteAddr(), server)
rc, err := client.Dial(server)
if err != nil {
conn.Close()
glog.Errorln(err)
continue
}
if rc, err = shadow.Shadow(rc, password); err != nil {
glog.Fatalln(err)
}
go relay(conn, rc)
}
}
func listenLocal(ports []string) <-chan net.Conn {
connCh := make(chan net.Conn, 10)
for i := range ports {
go func(port string) {
ln, err := net.Listen("tcp", port)
if err != nil {
glog.Fatalln(err)
}
for {
conn, err := ln.Accept()
if err != nil {
glog.Errorln("accept", port, "fail:", err)
continue
}
connCh <- conn
}
}(ports[i])
}
glog.Infoln("listening ports:", ports)
return connCh
}
+92
View File
@@ -0,0 +1,92 @@
package proxy
import (
"net"
"strings"
"time"
"github.com/miekg/dns"
"github.com/wweir/sower/dhcp"
"github.com/wweir/utils/log"
)
func StartDNS(redirectIP, relayServer string, shouldProxy func(string) bool) {
serveIP := net.ParseIP(redirectIP)
if redirectIP == "" || serveIP.String() != redirectIP {
log.Fatalw("invalid listen ip", "ip", redirectIP)
}
var err error
if relayServer, err = pickRelayAddr(relayServer); err != nil {
log.Fatalw("pick upstream dns server", "err", err)
}
log.Infow("upstream dns", "addr", relayServer)
dns.HandleFunc(".", func(w dns.ResponseWriter, r *dns.Msg) {
// *Msg r has an TSIG record and it was validated
if r.IsTsig() != nil && w.TsigStatus() == nil {
lastTsig := r.Extra[len(r.Extra)-1].(*dns.TSIG)
r.SetTsig(lastTsig.Hdr.Name, dns.HmacMD5, 300, time.Now().Unix())
}
//https://stackoverflow.com/questions/4082081/requesting-a-and-aaaa-records-in-single-dns-query/4083071#4083071
if len(r.Question) == 0 {
return
}
domain := r.Question[0].Name
if idx := strings.IndexByte(domain, ':'); idx > 0 {
domain = domain[:idx] // trim port
}
if shouldProxy(domain) {
w.WriteMsg(localA(r, domain, serveIP))
} else if msg, err := dns.Exchange(r, relayServer); err != nil || msg == nil {
server, err := pickRelayAddr(relayServer)
if err != nil {
log.Errorw("detect upstream dns", "err", err)
} else if relayServer != server {
relayServer = server
log.Infow("detect upstream dns", "addr", relayServer)
}
} else {
w.WriteMsg(msg)
}
})
server := &dns.Server{Addr: net.JoinHostPort(redirectIP, "53"), Net: "udp"}
log.Infow("start dns", "addr", server.Addr)
log.Fatalw("dns serve fail", "err", server.ListenAndServe())
}
func pickRelayAddr(relayServer string) (_ string, err error) {
if relayServer == "" {
if relayServer, err = dhcp.GetDefaultDNSServer(); err != nil {
return "", err
}
}
if _, _, err := net.SplitHostPort(relayServer); err != nil {
return net.JoinHostPort(relayServer, "53"), nil
}
return relayServer, nil
}
func localA(r *dns.Msg, domain string, localIP net.IP) *dns.Msg {
m := new(dns.Msg)
m.SetReply(r)
if localIP.To4() != nil {
m.Answer = []dns.RR{&dns.A{
Hdr: dns.RR_Header{Name: domain, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 20},
A: localIP,
}}
} else {
m.Answer = []dns.RR{&dns.AAAA{
Hdr: dns.RR_Header{Name: domain, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 20},
AAAA: localIP,
}}
}
return m
}
+85
View File
@@ -0,0 +1,85 @@
package proxy
import (
"context"
"crypto/tls"
"net"
"net/http"
"net/http/httputil"
"time"
"github.com/wweir/sower/transport"
"github.com/wweir/sower/util"
"github.com/wweir/utils/log"
)
// StartHTTPProxy start http reverse proxy.
// The httputil.ReverseProxy do not supply enough support for https request.
func StartHTTPProxy(httpProxyAddr, serverAddr string, password []byte,
shouldProxy func(string) bool) {
proxy := httputil.ReverseProxy{
Director: func(r *http.Request) {},
Transport: &http.Transport{
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
addr, _ = util.WithDefaultPort(addr, "80")
return transport.Dial(serverAddr, func(host string) (string, []byte) {
if shouldProxy(host) {
return httpProxyAddr, password
}
return "", nil
})
},
},
}
srv := &http.Server{
Addr: httpProxyAddr,
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodConnect {
httpsProxy(w, r, serverAddr, password, shouldProxy)
} else {
proxy.ServeHTTP(w, r)
}
}),
// Disable HTTP/2.
TLSNextProto: map[string]func(*http.Server, *tls.Conn, http.Handler){},
IdleTimeout: 90 * time.Second,
}
log.Infow("start sower http proxy", "http_proxy", httpProxyAddr)
go log.Fatalw("serve http proxy", "addr", httpProxyAddr, "err", srv.ListenAndServe())
}
func httpsProxy(w http.ResponseWriter, r *http.Request,
serverAddr string, password []byte, shouldProxy func(string) bool) {
conn, _, err := w.(http.Hijacker).Hijack()
if err != nil {
http.Error(w, err.Error(), http.StatusServiceUnavailable)
return
}
conn.(*net.TCPConn).SetKeepAlive(true)
defer conn.Close()
if _, err := conn.Write([]byte(r.Proto + " 200 Connection established\r\n\r\n")); err != nil {
http.Error(w, err.Error(), http.StatusServiceUnavailable)
return
}
target, _ := util.WithDefaultPort(r.Host, "443")
rc, err := transport.Dial(target, func(host string) (string, []byte) {
if shouldProxy(host) {
return serverAddr, password
}
return "", nil
})
if err != nil {
conn.Write([]byte("sower dial " + serverAddr + " fail: " + err.Error()))
conn.Close()
return
}
defer rc.Close()
relay(conn, rc)
}
-65
View File
@@ -1,65 +0,0 @@
package kcp
import (
"net"
"github.com/pkg/errors"
kcp "github.com/xtaci/kcp-go"
)
type client struct {
DataShard int
ParityShard int
DSCP int
SockBuf int
AckNodelay bool
NoDelay int
Interval int
Resend int
NoCongestion int
SndWnd int
RcvWnd int
MTU int
}
func NewClient() *client {
return &client{
DataShard: 10,
ParityShard: 3,
DSCP: 0,
SockBuf: 4194304,
NoDelay: 0,
Interval: 50,
Resend: 0,
NoCongestion: 0,
SndWnd: 0,
RcvWnd: 0,
MTU: 1350,
}
}
func (c *client) Dial(server string) (net.Conn, error) {
conn, err := kcp.DialWithOptions(server, nil, c.DataShard, c.ParityShard)
if err != nil {
return nil, errors.Wrap(err, "dial")
}
conn.SetStreamMode(true)
conn.SetWriteDelay(false)
conn.SetNoDelay(c.NoDelay, c.Interval, c.Resend, c.NoCongestion)
conn.SetWindowSize(c.SndWnd, c.RcvWnd)
conn.SetMtu(c.MTU)
conn.SetACKNoDelay(c.AckNodelay)
if err := conn.SetDSCP(c.DSCP); err != nil {
return nil, errors.Wrap(err, "SetDSCP")
}
if err := conn.SetReadBuffer(c.SockBuf); err != nil {
return nil, errors.Wrap(err, "SetReadBuffer")
}
if err := conn.SetWriteBuffer(c.SockBuf); err != nil {
return nil, errors.Wrap(err, "SetWriteBuffer")
}
return conn, nil
}
-56
View File
@@ -1,56 +0,0 @@
package kcp
import (
"net"
"github.com/golang/glog"
"github.com/pkg/errors"
kcp "github.com/xtaci/kcp-go"
)
type server struct {
DataShard int
ParityShard int
DSCP int
SockBuf int
}
func NewServer(password string) *server {
return &server{
DataShard: 10,
ParityShard: 3,
DSCP: 0,
SockBuf: 4194304,
}
}
func (s *server) Listen(port string) (<-chan net.Conn, error) {
ln, err := kcp.ListenWithOptions(port, nil, s.DataShard, s.ParityShard)
if err != nil {
return nil, err
}
if err := ln.SetDSCP(s.DSCP); err != nil {
return nil, errors.Wrap(err, "SetDSCP")
}
if err := ln.SetReadBuffer(s.SockBuf); err != nil {
return nil, errors.Wrap(err, "SetReadBuffer")
}
if err := ln.SetWriteBuffer(s.SockBuf); err != nil {
return nil, errors.Wrap(err, "SetWriteBuffer")
}
connCh := make(chan net.Conn)
go func() {
for {
conn, err := ln.AcceptKCP()
if err != nil {
glog.Fatalln("KCP listen:", err)
}
connCh <- conn
}
}()
return connCh, nil
}
-16
View File
@@ -1,16 +0,0 @@
// Code generated by "stringer -type=netType util.go"; DO NOT EDIT.
package proxy
import "strconv"
const _netType_name = "QUICKCPTCP"
var _netType_index = [...]uint8{0, 4, 7, 10}
func (i netType) String() string {
if i < 0 || i >= netType(len(_netType_index)-1) {
return "netType(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _netType_name[_netType_index[i]:_netType_index[i+1]]
}
+141
View File
@@ -0,0 +1,141 @@
package proxy
import (
"crypto/tls"
"net"
"net/http"
"github.com/wweir/sower/transport"
"github.com/wweir/utils/log"
"golang.org/x/crypto/acme/autocert"
)
func StartClient(serverAddr, password string, enableDNS bool,
forwards map[string]string, shouldProxy func(string) bool) {
passwordData := []byte(password)
relayToRemote := func(lnAddr, target string,
parseFn func(net.Conn) (net.Conn, string, error),
shouldProxy func(string) bool) {
ln, err := net.Listen("tcp", lnAddr)
if err != nil {
log.Fatalw("tcp listen", "port", lnAddr, "err", err)
}
for {
conn, err := ln.Accept()
if err != nil {
log.Errorw("tcp accept", "port", lnAddr, "err", err)
continue
}
go func(conn net.Conn) {
defer conn.Close()
if parseFn != nil {
if conn, target, err = parseFn(conn); err != nil {
log.Warnw("parse target", "err", err)
return
}
}
rc, err := transport.Dial(target, func(domain string) (string, []byte) {
if shouldProxy(domain) {
return serverAddr, passwordData
}
return "", nil
})
if err != nil {
host, _, _ := net.SplitHostPort(target)
log.Warnw("dial", "addr", target, "proxy", shouldProxy(host), "err", err)
return
}
defer rc.Close()
relay(conn, rc)
}(conn)
}
}
for from, to := range forwards {
go relayToRemote(from, to, nil, func(string) bool { return true })
}
if enableDNS {
go relayToRemote(":80", "", ParseHTTP, shouldProxy)
go relayToRemote(":443", "", ParseHTTPS, shouldProxy)
}
log.Infow("start sower client", "dns solution", enableDNS, "forwards", forwards)
select {}
}
func StartServer(relayTarget, password, cacheDir, certFile, keyFile, email string) {
certManager := autocert.Manager{
Prompt: autocert.AcceptTOS,
Email: email,
Cache: autocert.DirCache(cacheDir),
}
tlsConf := &tls.Config{
GetCertificate: certManager.GetCertificate,
MinVersion: tls.VersionTLS12,
NextProtos: []string{"http/1.1", "h2"},
}
if certFile != "" && keyFile != "" {
if cert, err := tls.LoadX509KeyPair(certFile, keyFile); err != nil {
log.Fatalw("load certificate", "cert", certFile, "key", keyFile, "err", err)
} else {
tlsConf.GetCertificate = nil
tlsConf.Certificates = []tls.Certificate{cert}
}
}
// Try to redirect 80 to 443
go http.ListenAndServe(":80", certManager.HTTPHandler(http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
r.URL.Scheme = "https"
if host, _, err := net.SplitHostPort(r.Host); err != nil {
r.URL.Host = r.Host
} else {
r.URL.Host = host
}
http.Redirect(w, r, r.URL.String(), 301)
})))
log.Infow("start sower server", "relay_to", relayTarget)
ln, err := tls.Listen("tcp", ":443", tlsConf)
if err != nil {
log.Fatalw("tcp listen", "err", err)
}
passwordData := []byte(password)
for {
conn, err := ln.Accept()
if err != nil {
log.Errorw("tcp accept", "err", err)
continue
}
go func(conn net.Conn) {
defer conn.Close()
conn, target := transport.ParseProxyConn(conn, passwordData)
if target == "" {
target = relayTarget
}
rc, err := net.Dial("tcp", target)
if err != nil {
log.Errorw("tcp dial", "addr", target, "err", err)
return
}
defer rc.Close()
relay(conn, rc)
}(conn)
}
}
-50
View File
@@ -1,50 +0,0 @@
package quic
import (
"crypto/tls"
"net"
"time"
quic "github.com/lucas-clemente/quic-go"
"github.com/pkg/errors"
)
type client struct {
conf *quic.Config
sess quic.Session
}
func NewClient() *client {
return &client{
// conf: &quic.Config{
// HandshakeTimeout: 5 * time.Second,
// MaxIncomingStreams: 1024,
// KeepAlive: true,
// },
}
}
func (c *client) Dial(server string) (net.Conn, error) {
if c.sess == nil {
if sess, err := quic.DialAddr(server, &tls.Config{InsecureSkipVerify: true}, c.conf); err != nil {
return nil, errors.Wrap(err, "session")
} else {
c.sess = sess
}
}
var stream quic.Stream
if err := WithTimeout(func() (err error) {
if stream, err = c.sess.OpenStream(); err != nil {
c.sess = nil
}
return
}, time.Second); err != nil {
return nil, errors.Wrap(err, "stream")
}
return &streamConn{
Stream: stream,
sess: c.sess,
}, nil
}
-57
View File
@@ -1,57 +0,0 @@
package quic
import (
"net"
"github.com/golang/glog"
quic "github.com/lucas-clemente/quic-go"
"github.com/pkg/errors"
)
type server struct {
conf *quic.Config
}
func NewServer() *server {
return &server{
// conf: &quic.Config{
// HandshakeTimeout: 5 * time.Second,
// MaxIncomingStreams: 1024,
// KeepAlive: true,
// },
}
}
func (s *server) Listen(port string) (<-chan net.Conn, error) {
ln, err := quic.ListenAddr(port, mockTlsPem(), s.conf)
if err != nil {
return nil, errors.WithStack(err)
}
connCh := make(chan net.Conn)
go func() {
for {
sess, err := ln.Accept()
if err != nil {
glog.Fatalln(err)
}
go accept(sess, connCh)
}
}()
return connCh, nil
}
func accept(sess quic.Session, connCh chan<- net.Conn) {
glog.V(1).Infoln("new session from ", sess.RemoteAddr())
defer sess.Close()
for {
stream, err := sess.AcceptStream()
if err != nil {
glog.Errorln(err)
return
}
connCh <- &streamConn{stream, sess}
}
}
-67
View File
@@ -1,67 +0,0 @@
package quic
import (
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"math/big"
"net"
"time"
"github.com/golang/glog"
quic "github.com/lucas-clemente/quic-go"
"github.com/pkg/errors"
)
type streamConn struct {
quic.Stream
sess quic.Session
}
func (s *streamConn) LocalAddr() net.Addr {
return s.sess.LocalAddr()
}
func (s *streamConn) RemoteAddr() net.Addr {
return s.sess.RemoteAddr()
}
func mockTlsPem() *tls.Config {
key, err := rsa.GenerateKey(rand.Reader, 1024)
if err != nil {
glog.Fatalln(err)
}
template := x509.Certificate{SerialNumber: big.NewInt(1)}
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
if err != nil {
glog.Fatalln(err)
}
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
if err != nil {
glog.Fatalln(err)
}
return &tls.Config{Certificates: []tls.Certificate{tlsCert}}
}
func WithTimeout(fn func() error, timeout time.Duration) error {
var okCh = make(chan struct{})
var err error
go func() {
err = fn()
close(okCh)
}()
select {
case <-okCh:
return err
case <-time.After(timeout):
return errors.New("timeout: " + timeout.String())
}
}
-71
View File
@@ -1,71 +0,0 @@
package proxy
import (
"net"
"strings"
"github.com/golang/glog"
"github.com/wweir/sower/parse"
"github.com/wweir/sower/proxy/kcp"
"github.com/wweir/sower/proxy/quic"
"github.com/wweir/sower/proxy/tcp"
"github.com/wweir/sower/shadow"
)
type Server interface {
Listen(port string) (<-chan net.Conn, error)
}
func StartServer(netType, port, password string) {
var server Server
switch netType {
case QUIC.String():
server = quic.NewServer()
case KCP.String():
server = kcp.NewServer(password)
case TCP.String():
server = tcp.NewServer()
}
if port == "" {
glog.Fatalln("port must set")
}
if !strings.Contains(port, ":") {
port = ":" + port
}
connCh, err := server.Listen(port)
if err != nil {
glog.Fatalf("listen %v fail: %s", port, err)
}
for {
conn := <-connCh
conn, err := shadow.Shadow(conn, password)
if err != nil {
glog.Fatalln(err)
}
go handle(conn)
}
}
func handle(conn net.Conn) {
conn, addr, err := parse.ParseAddr(conn)
if err != nil {
glog.Warningln(err)
return
}
glog.V(1).Infof("new conn from %s to %s", conn.RemoteAddr(), addr)
rc, err := net.Dial("tcp", addr)
if err != nil {
glog.Warningln(err)
return
}
if err := rc.(*net.TCPConn).SetKeepAlive(true); err != nil {
glog.Warningln(err)
}
relay(rc, conn)
}
-14
View File
@@ -1,14 +0,0 @@
package tcp
import "net"
type client struct {
}
func NewClient() *client {
return &client{}
}
func (c *client) Dial(server string) (net.Conn, error) {
return net.Dial("tcp", server)
}
-34
View File
@@ -1,34 +0,0 @@
package tcp
import (
"net"
"github.com/golang/glog"
)
type server struct {
}
func NewServer() *server {
return &server{}
}
func (s *server) Listen(port string) (<-chan net.Conn, error) {
ln, err := net.Listen("tcp", port)
if err != nil {
return nil, err
}
connCh := make(chan net.Conn)
go func() {
for {
conn, err := ln.Accept()
if err != nil {
glog.Fatalln("TCP listen:", err)
}
connCh <- conn
}
}()
return connCh, nil
}
+35 -17
View File
@@ -1,23 +1,46 @@
package proxy
import (
"bufio"
"crypto/tls"
"io"
"net"
"net/http"
"sync"
"sync/atomic"
"time"
"github.com/golang/glog"
"github.com/wweir/sower/util"
)
//go:generate stringer -type=netType $GOFILE
type netType int
func ParseHTTP(conn net.Conn) (net.Conn, string, error) {
teeConn := &util.TeeConn{Conn: conn}
defer teeConn.Stop()
const (
QUIC netType = iota
KCP
TCP
)
resp, err := http.ReadRequest(bufio.NewReader(teeConn))
if err != nil {
return teeConn, "", err
}
resp.Host, _ = util.WithDefaultPort(resp.Host, "80")
return teeConn, resp.Host, nil
}
func ParseHTTPS(conn net.Conn) (net.Conn, string, error) {
teeConn := &util.TeeConn{Conn: conn}
defer teeConn.Stop()
var domain string
tls.Server(teeConn, &tls.Config{
GetConfigForClient: func(hello *tls.ClientHelloInfo) (*tls.Config, error) {
domain = hello.ServerName
return nil, nil
},
}).Handshake()
domain, _ = util.WithDefaultPort(domain, "443")
return teeConn, domain, nil
}
func relay(conn1, conn2 net.Conn) {
wg := &sync.WaitGroup{}
@@ -27,19 +50,14 @@ func relay(conn1, conn2 net.Conn) {
redirect(conn1, conn2, wg, exitFlag)
wg.Wait()
}
func redirect(dst, src net.Conn, wg *sync.WaitGroup, exitFlag *int32) {
if _, err := io.Copy(dst, src); err != nil {
glog.V(1).Infof("%s<>%s -> %s<>%s: %s", src.RemoteAddr(), src.LocalAddr(), dst.LocalAddr(), dst.RemoteAddr(), err)
}
io.Copy(dst, src)
if atomic.CompareAndSwapInt32(exitFlag, 0, 1) {
// wakeup blocked goroutine
now := time.Now()
src.SetReadDeadline(now)
dst.SetWriteDeadline(now)
} else {
src.Close()
dst.Close()
src.SetDeadline(now)
dst.SetDeadline(now)
}
wg.Done()
+124
View File
@@ -0,0 +1,124 @@
package router
import (
"bytes"
"crypto/tls"
"encoding/binary"
"io"
"net"
"strconv"
"time"
)
// Port ==========================
type Port uint16
const (
HTTP Port = 80
HTTPS Port = 443
)
// Ping try connect to a http(s) server with domain though the http addr
func (p Port) Ping(domain string, dial func(string) (net.Conn, error)) error {
conn, err := dial(net.JoinHostPort(domain, p.String()))
if err != nil {
return err
}
defer conn.Close()
conn.SetDeadline(time.Now().Add(5 * time.Second))
if _, err := conn.Write(p.PingMsg(domain)); err != nil {
return err
}
// err -> nil: read something succ
// err -> io.EOF: no such domain or connection refused
// err -> timeout: tcp package has been dropped
if _, err = conn.Read(make([]byte, 10)); err == io.EOF {
err = nil
}
return err
}
func (p Port) String() string {
return strconv.Itoa(int(p))
}
func (p Port) PingMsg(domain string) []byte {
switch p {
case HTTP:
return []byte("TRACE / HTTP/1.1\r\nHost: " + domain + "\r\n\r\n")
case HTTPS:
return NewClientHelloSNIMsg(domain)
default:
panic("invalid port")
}
}
// SNI ==========================
type clientHelloSNI struct {
ContentType uint8
Version uint16
Length uint16
handshakeProtocol
}
type handshakeProtocol struct {
HandshakeType uint8
LengthExpand uint8
Length uint16
Version uint16
Random [32]byte
SessionIDLength uint8
CipherSuitesLength uint16
CipherSuite uint16
CompressionMethodsLength uint8
CompressionMethod uint8
ExtensionsLength uint16
extensionServerName
}
type extensionServerName struct {
Type uint16
Length uint16
serverNameIndicationExtension
}
type serverNameIndicationExtension struct {
ServerNameListlength uint16
ServerNameType uint8
ServerNamelength uint16
// ServerName []byte // Disable for fix length
}
func NewClientHelloSNIMsg(domain string) []byte {
length := uint16(len(domain))
msg := &clientHelloSNI{
ContentType: 0x16, // Content Type: Handshake (22)
Version: 0x0301, // Version: TLS 1.0
Length: length + 56,
handshakeProtocol: handshakeProtocol{
HandshakeType: 0x01, // Handshake Type: Client Hello (1)
Length: length + 52,
Version: 0x0303, // Version: TLS 1.2 (0x0303)
Random: [32]byte{}, // [32]byte{},
SessionIDLength: 0x0, // Session ID Length: 0
CipherSuitesLength: 2, // Cipher Suites Length: 84
CipherSuite: tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
CompressionMethodsLength: 1, // Compression Methods Length: 1
CompressionMethod: 0x00, // Compression null
ExtensionsLength: length + 9,
extensionServerName: extensionServerName{
Type: 0x0000, // Type: server_name (0)
Length: length + 5,
serverNameIndicationExtension: serverNameIndicationExtension{
ServerNameListlength: length + 3,
ServerNameType: 0x00, // Server Name Type: host_name (0)
ServerNamelength: length,
},
},
},
}
buf := bytes.NewBuffer(make([]byte, 0, length+61))
binary.Write(buf, binary.BigEndian, msg)
buf.WriteString(domain)
return buf.Bytes()
}
+72
View File
@@ -0,0 +1,72 @@
// +build debug
// go test -v -tags debug
package router_test
import (
"crypto/tls"
"net"
"strconv"
"testing"
"time"
"github.com/wweir/sower/router"
"github.com/wweir/sower/transport"
)
var (
proxyAddr string = "socks5://127.0.0.1:1080"
password []byte = nil
)
func TestPort_Ping(t *testing.T) {
direct := func(addr string) (net.Conn, error) {
return net.DialTimeout("tcp", addr, 5*time.Second)
}
proxy := func(addr string) (net.Conn, error) {
conn, err := tls.Dial("tcp", proxyAddr, &tls.Config{})
if err != nil {
return nil, err
}
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
p, err := strconv.Atoi(port)
if err != nil {
return nil, err
}
return transport.ToProxyConn(conn, host, uint16(p), password)
}
type args struct {
domain string
dial func(string) (net.Conn, error)
}
tests := []struct {
name string
p router.Port
args args
wantErr bool
}{
{"", router.HTTP, args{"baidu.com", direct}, false},
{"", router.HTTP, args{"baidu.com", proxy}, false},
{"", router.HTTPS, args{"baidu.com", direct}, false},
{"", router.HTTPS, args{"baidu.com", proxy}, false},
{"", router.HTTP, args{"google.com", direct}, true},
{"", router.HTTP, args{"google.com", proxy}, false},
{"", router.HTTPS, args{"google.com", direct}, true},
{"", router.HTTPS, args{"google.com", proxy}, false},
{"", router.HTTP, args{"smtp.163.com", direct}, true},
{"", router.HTTP, args{"smtp.163.com", proxy}, true},
{"", router.HTTPS, args{"smtp.163.com", direct}, true},
{"", router.HTTPS, args{"smtp.163.com", proxy}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := tt.p.Ping(tt.args.domain, tt.args.dial); (err != nil) != tt.wantErr {
t.Errorf("Port.Ping() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
+143
View File
@@ -0,0 +1,143 @@
package router
import (
"net"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/wweir/sower/transport"
"github.com/wweir/sower/util"
"github.com/wweir/utils/log"
"github.com/wweir/utils/mem"
)
// Route implement a router for each request
type Route struct {
once sync.Once
cache *mem.Cache
ProxyAddress string
ProxyPassword string
password []byte
DetectLevel int // dynamic detect proxy level
DirectList []string
directRule *util.Node
ProxyList []string
proxyRule *util.Node
PersistFn func(string)
}
// ShouldProxy check if the domain shoule request though proxy
func (r *Route) GenProxyCheck(sync bool) func(string) bool {
r.once.Do(func() {
r.cache = mem.New(4 * time.Hour)
r.password = []byte(r.ProxyPassword)
r.directRule = util.NewNodeFromRules(r.DirectList...)
r.proxyRule = util.NewNodeFromRules(r.ProxyList...)
})
detect := func(domain string) bool {
go r.cache.Remember(r, domain)
return true
}
if sync {
detect = func(domain string) bool {
r.cache.Remember(r, domain)
if r.proxyRule.Match(domain) {
return true
}
return !r.directRule.Match(domain)
}
}
return func(domain string) bool {
domain = strings.TrimSuffix(domain, ".")
// break deadlook, for wildcard
if sepCount := strings.Count(domain, "."); sepCount == 0 || sepCount >= 5 {
return false
}
if r.proxyRule.Match(domain) {
return true
}
if r.directRule.Match(domain) {
return false
}
return detect(domain)
}
}
// Get implement for cache
func (r *Route) Get(key interface{}) (err error) {
domain := key.(string)
httpScore, httpsScore := r.detect(domain)
log.Infow("detect", "domain", domain, "http", httpScore, "https", httpsScore)
if httpScore+httpsScore >= r.DetectLevel {
r.directRule.Add(domain)
if r.PersistFn != nil {
r.PersistFn(domain)
}
}
return nil
}
// detect and caculate direct connection and proxy connection score
func (r *Route) detect(domain string) (http, https int) {
wg := sync.WaitGroup{}
httpScore, httpsScore := new(int32), new(int32)
for _, ping := range [...]struct {
shouldProxy bool
port Port
}{
{shouldProxy: true, port: HTTP},
{shouldProxy: true, port: HTTPS},
{shouldProxy: false, port: HTTP},
{shouldProxy: false, port: HTTPS},
} {
wg.Add(1)
go func(shouldProxy bool, port Port) {
defer wg.Done()
if err := port.Ping(domain, func(domain string) (net.Conn, error) {
return transport.Dial(domain,
func(domain string) (proxyAddr string, password []byte) {
if shouldProxy {
return r.ProxyAddress, r.password
}
return "", nil
})
}); err != nil {
log.Warnw("sower dial", "proxy", shouldProxy, "host", domain, "port", port, "err", err)
return
}
switch {
case shouldProxy && port == HTTP:
if !atomic.CompareAndSwapInt32(httpScore, 0, -2) {
atomic.AddInt32(httpScore, -1)
}
case shouldProxy && port == HTTPS:
if !atomic.CompareAndSwapInt32(httpsScore, 0, -2) {
atomic.AddInt32(httpsScore, -1)
}
case !shouldProxy && port == HTTP:
if !atomic.CompareAndSwapInt32(httpScore, 0, 2) {
atomic.AddInt32(httpScore, 1)
}
case !shouldProxy && port == HTTPS:
if !atomic.CompareAndSwapInt32(httpsScore, 0, 2) {
atomic.AddInt32(httpsScore, 1)
}
}
}(ping.shouldProxy, ping.port)
}
wg.Wait()
return int(*httpScore), int(*httpsScore)
}
-6
View File
@@ -1,6 +0,0 @@
// transter conn to be a crypto conn
// stream mode, blocksize is 1350, for mtu
// data [0 ... 1332]
// block [size][2 ... 1350]
// payload size + data size + aead overhead => (2+1332+16)
package shadow
-137
View File
@@ -1,137 +0,0 @@
package shadow
import (
"crypto/aes"
"crypto/cipher"
"encoding/binary"
"io"
"log"
"math/rand"
"net"
"github.com/pkg/errors"
)
func init() {
log.SetFlags(log.Ltime | log.Lshortfile)
}
var MTU = 1350 //MTU must little than 0xFFFF
type conn struct {
dataSize int
aead cipher.AEAD
encryptNonce func() []byte
decryptNonce func() []byte
writeBuf []byte
readBuf []byte
readOffset int
readLast int
net.Conn
}
func (c *conn) Read(b []byte) (n int, err error) {
bLength := len(b)
offset := 0
switch c.readOffset {
case -1:
return 0, io.EOF
case 0: // read from conn
default: // read from buffer
offset = copy(b, c.readBuf[c.readOffset:c.readLast])
if offset+c.readOffset < c.readLast {
c.readOffset += offset
return offset, nil
}
if c.readLast < c.dataSize {
c.readOffset = -1
return offset, io.EOF
}
c.readOffset = 0
}
// read from conn
for ; offset < bLength; offset += c.readOffset {
_, err = io.ReadFull(c.Conn, c.readBuf)
if err != nil && err != io.EOF {
c.readOffset = 0
return offset, err
}
_, e := c.aead.Open(c.readBuf[:0], c.decryptNonce(), c.readBuf, nil)
if e != nil {
c.readOffset = 0
return offset, e
}
c.readLast = int(c.readBuf[0])<<8 + int(c.readBuf[1]) + 2
c.readOffset = copy(b[offset:], c.readBuf[2:2+c.readLast])
if c.readOffset < c.readLast {
c.readOffset += 2
return len(b), nil
} else if err == io.EOF {
readOffset := c.readOffset
c.readOffset = 0
return readOffset, err
}
}
return len(b), err
}
func (c *conn) Write(b []byte) (n int, err error) {
bLength := len(b)
size := 0
for offset := 0; offset < bLength; offset += size {
if offset+c.dataSize <= bLength {
size = c.dataSize
} else {
size = bLength - offset
}
// BigEndian
c.writeBuf[0], c.writeBuf[1] = byte((size&0xFF00)>>8), byte(size&0xFF)
copy(c.writeBuf[2:size+2], b[offset:offset+size])
c.aead.Seal(c.writeBuf[:0], c.encryptNonce(), c.writeBuf[:c.dataSize+2], nil)
_, err = c.Conn.Write(c.writeBuf)
if err != nil {
return offset, err
}
}
return len(b), err
}
func Shadow(c net.Conn, password string) (net.Conn, error) {
block, err := aes.NewCipher([]byte(password + password)[:16])
if err != nil {
return nil, errors.Wrap(err, "password too short")
}
aead, err := cipher.NewGCM(block)
if err != nil {
return nil, errors.Wrap(err, "GCM")
}
return &conn{
dataSize: MTU - aead.Overhead() - 2,
aead: aead,
encryptNonce: newNonce(password, aead.NonceSize()),
decryptNonce: newNonce(password, aead.NonceSize()),
writeBuf: make([]byte, MTU),
readBuf: make([]byte, MTU),
Conn: c,
}, nil
}
func newNonce(password string, size int) func() []byte {
num, _ := binary.Varint([]byte(password))
rnd := rand.New(rand.NewSource(num))
buf := make([]byte, size)
return func() []byte {
rnd.Read(buf)
return buf
}
}
+68
View File
@@ -0,0 +1,68 @@
package transport
import (
"crypto/md5"
"encoding/binary"
"io"
"net"
"strconv"
"github.com/wweir/sower/util"
)
// checksum(>=0x80) + port + target_length + target + data
// data(HTTP, first byte < 0x7F)
type head struct {
Checksum byte
Port uint16
AddrLen uint8
}
func ParseProxyConn(conn net.Conn, password []byte) (net.Conn, string) {
teeConn := &util.TeeConn{Conn: conn}
defer teeConn.Stop()
h := &head{}
if err := binary.Read(teeConn, binary.BigEndian, h); err != nil || h.Checksum < 0x80 {
return teeConn, ""
}
buf := make([]byte, int(h.AddrLen))
if _, err := io.ReadFull(teeConn, buf); err != nil {
return teeConn, ""
}
if h.Checksum != sumChecksum(buf, password) {
return teeConn, ""
}
teeConn.Reset()
return teeConn, net.JoinHostPort(string(buf), strconv.Itoa(int(h.Port)))
}
func ToProxyConn(conn net.Conn, tgtHost string, tgtPort uint16, password []byte) (net.Conn, error) {
h := &head{
Checksum: sumChecksum([]byte(tgtHost), password),
Port: tgtPort,
AddrLen: uint8(len(tgtHost)),
}
if err := binary.Write(conn, binary.BigEndian, h); err != nil {
conn.Close()
return nil, err
}
var data = []byte(tgtHost)
var err error
for n, nn := 0, 0; nn < len(data); nn += n {
if n, err = conn.Write(data[nn:]); err != nil {
conn.Close()
return nil, err
}
}
return conn, nil
}
func sumChecksum(target, password []byte) byte {
return md5.Sum(append(target, password...))[0] | 0x80
}
+125
View File
@@ -0,0 +1,125 @@
package transport
import (
"encoding/binary"
"fmt"
"io"
"net"
"strings"
)
func IsSocks5Schema(addr string) (string, bool) {
if strings.HasPrefix(addr, "socks5://") {
return strings.TrimPrefix(addr, "socks5://"), true
}
if strings.HasPrefix(addr, "socks5h://") {
return strings.TrimPrefix(addr, "socks5h://"), true
}
return addr, false
}
func ToSocks5(c net.Conn, host string, port uint16) (net.Conn, error) {
return &conn{
init: make(chan struct{}),
Conn: c,
domain: host,
port: port,
}, nil
}
type conn struct {
init chan struct{}
domain string
port uint16
net.Conn
}
func (c *conn) Read(b []byte) (n int, err error) {
<-c.init
return c.Conn.Read(b)
}
func (c *conn) Write(b []byte) (n int, err error) {
select {
case <-c.init:
return c.Conn.Write(b)
default:
}
{
req := &authReq{
VER: 5,
NMETHODS: 1,
METHODS: [1]byte{0}, // NO AUTHENTICATION REQUIRED
}
if err := binary.Write(c.Conn, binary.BigEndian, req); err != nil {
return 0, err
}
}
{
resp := &authResp{}
if err := binary.Read(c.Conn, binary.BigEndian, resp); err != nil {
return 0, err
}
}
{
portBuf := make([]byte, 2)
binary.BigEndian.PutUint16(portBuf, c.port)
req := &request{
req: req{
VER: 5, // socks5
CMD: 1, // CONNECT
RSV: 0, // RESERVED
ATYP: 3, // DOMAINNAME
},
DST_ADDR: append([]byte{byte(len(c.domain))}, []byte(c.domain)...),
DST_PORT: portBuf,
}
if _, err := c.Conn.Write(req.Bytes()); err != nil {
return 0, err
}
}
{
resp := &response{}
if err := binary.Read(c.Conn, binary.BigEndian, &(resp.resp)); err != nil {
return 0, err
}
switch resp.REP {
case 0x00:
default:
return 0, fmt.Errorf("socks5 handshake fail, return code: %d", resp.REP)
}
switch resp.ATYP {
case 0x01: // IPv4
resp.DST_ADDR = make([]byte, net.IPv4len)
if _, err := io.ReadFull(c.Conn, resp.DST_ADDR); err != nil {
return 0, err
}
case 0x03: // domain name
if _, err := io.ReadFull(c.Conn, resp.DST_ADDR[:1]); err != nil {
return 0, err
}
if _, err := io.ReadFull(c.Conn, resp.DST_ADDR[1:1+int(resp.DST_ADDR[0])]); err != nil {
return 0, err
}
case 0x04:
resp.DST_ADDR = make([]byte, net.IPv6len)
if _, err := io.ReadFull(c.Conn, resp.DST_ADDR); err != nil {
return 0, err
}
}
resp.DST_PORT = make([]byte, 2)
if _, err := io.ReadFull(c.Conn, resp.DST_PORT); err != nil {
return 0, err
}
}
close(c.init)
return c.Conn.Write(b)
}
+44
View File
@@ -0,0 +1,44 @@
package transport
// https://tools.ietf.org/html/rfc1928
type authReq struct {
VER byte
NMETHODS byte
METHODS [1]byte // 1 to 255, fix to no authentication
}
type authResp struct {
VER byte
METHOD byte
}
type request struct {
req
DST_ADDR []byte // first byte is length
DST_PORT []byte // two bytes
}
type req struct {
VER byte
CMD byte
RSV byte
ATYP byte
}
func (r *request) Bytes() []byte {
out := []byte{r.VER, r.CMD, r.RSV, r.ATYP}
out = append(out, r.DST_ADDR...)
return append(out, r.DST_PORT...)
}
type response struct {
resp
DST_ADDR []byte // first byte is length
DST_PORT []byte // two bytes
}
type resp struct {
VER byte
REP byte
RSV byte
ATYP byte
}
+89
View File
@@ -0,0 +1,89 @@
package transport
import (
"context"
"crypto/tls"
"net"
"strconv"
"github.com/wweir/sower/dhcp"
"github.com/wweir/utils/log"
)
var (
persistDNS string
dnsAddr string
resolver = &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
return (&net.Dialer{}).DialContext(ctx, network, dnsAddr)
},
}
)
func SetDNS(err error, dnsIP string) {
if dnsIP != "" {
persistDNS = dnsIP
dnsAddr = net.JoinHostPort(dnsIP, "53")
return
} else if persistDNS != "" {
return
}
if e, ok := err.(*net.DNSError); !ok /*nil*/ || !e.IsNotFound {
if dnsIP, err = dhcp.GetDefaultDNSServer(); err != nil {
dnsIP, err = dhcp.GetDefaultDNSServer() // retry
}
if err != nil {
log.Errorw("get dns via dhcp", "err", err, "current_dns", dnsAddr)
} else {
dnsAddr = net.JoinHostPort(dnsIP, "53")
}
}
}
// Dial dial targetAddr with possiable proxy address
func Dial(targetAddr string, dialAddr func(domain string) (proxyAddr string, password []byte)) (net.Conn, error) {
host, port, err := net.SplitHostPort(targetAddr)
if err != nil {
return nil, err
}
address, password := dialAddr(host)
if address == "" {
ips, err := resolver.LookupIPAddr(context.Background(), host)
if err != nil { //retry
ips, err = resolver.LookupIPAddr(context.Background(), host)
}
if err != nil {
SetDNS(err, "")
return nil, err
}
return net.Dial("tcp", net.JoinHostPort(ips[0].String(), port))
}
p, err := strconv.Atoi(port)
if err != nil {
return nil, err
}
if addr, ok := IsSocks5Schema(address); ok {
conn, err := net.Dial("tcp", addr)
if err != nil {
return nil, err
}
if conn, err = ToSocks5(conn, host, uint16(p)); err != nil {
conn.Close()
return nil, err
}
return conn, nil
}
conn, err := tls.DialWithDialer(&net.Dialer{Resolver: resolver},
"tcp", net.JoinHostPort(address, "443"), &tls.Config{})
if err != nil {
return nil, err
}
return ToProxyConn(conn, host, uint16(p), password)
}
+52
View File
@@ -0,0 +1,52 @@
package util
import (
"sort"
"strings"
)
type ReverseSecSlice struct {
sort.StringSlice
}
func NewReverseSecSlice(a []string) *ReverseSecSlice {
return &ReverseSecSlice{sort.StringSlice(a)}
}
func (p *ReverseSecSlice) Sort() *ReverseSecSlice {
sort.Sort(p)
return p
}
func (p *ReverseSecSlice) Uniq() []string {
olds := []string(p.StringSlice)
last := ""
strs := make([]string, 0, len(olds))
for _, str := range olds {
if str != last {
strs = append(strs, str)
}
last = str
}
return strs
}
func (p *ReverseSecSlice) Less(i, j int) bool {
secsI := strings.Split(p.StringSlice[i], ".")
secsJ := strings.Split(p.StringSlice[j], ".")
lenI := len(secsI) - 1
lenJ := len(secsJ) - 1
length := lenI
if lenI > lenJ {
length = lenJ
}
for idx := 0; idx <= length; idx++ {
if secsI[lenI-idx] == secsJ[lenJ-idx] {
continue
}
return secsI[lenI-idx] < secsJ[lenJ-idx]
}
return lenI < lenJ
}
+65
View File
@@ -0,0 +1,65 @@
package util
import (
"sort"
"testing"
)
func TestReverseSecSlice_Less(t *testing.T) {
type fields struct {
StringSlice sort.StringSlice
}
type args struct {
i int
j int
}
slice := fields{sort.StringSlice([]string{
"a.b.c",
"a.b.c",
"d.b.c",
"d.a.b.c",
"",
"",
})}
tests := []struct {
name string
fields fields
args args
want bool
}{{
name: "equal",
fields: slice,
args: args{0, 1},
want: false,
}, {
name: "less",
fields: slice,
args: args{0, 2},
want: true,
}, {
name: "length",
fields: slice,
args: args{0, 3},
want: true,
}, {
name: "single_empty",
fields: slice,
args: args{0, 4},
want: false,
}, {
name: "all_empty",
fields: slice,
args: args{4, 5},
want: false,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := &ReverseSecSlice{
StringSlice: tt.fields.StringSlice,
}
if got := p.Less(tt.args.i, tt.args.j); got != tt.want {
t.Errorf("ReverseSecSlice.Less() = %v, want %v", got, tt.want)
}
})
}
}
+108
View File
@@ -0,0 +1,108 @@
package util
import (
"strings"
"sync"
)
type Node struct {
node
sep string
*sync.RWMutex
}
type node struct {
node map[string]*node
}
func NewNodeFromRules(rules ...string) *Node {
n := &Node{node{node: map[string]*node{}}, ".", &sync.RWMutex{}}
for i := range rules {
n.Add(rules[i])
}
return n
}
func (n *Node) String() string {
n.RLock()
defer n.RUnlock()
return n.string("", " ")
}
func (n *node) string(prefix, indent string) (out string) {
for key, val := range n.node {
out += prefix + key + "\n" + val.string(prefix+indent, indent)
}
return
}
func (n *Node) trim(item string) string {
return strings.TrimSuffix(item, n.sep)
}
func (n *Node) Add(item string) {
n.Lock()
defer n.Unlock()
n.add(strings.Split(n.trim(item), n.sep))
}
func (n *node) add(secs []string) {
length := len(secs)
switch length {
case 0:
return
case 1:
n.node[secs[length-1]] = &node{node: map[string]*node{"": &node{}}}
default:
sec := secs[length-1]
if sec == "**" {
sec = "*"
}
subNode, ok := n.node[sec]
if !ok {
subNode = &node{node: map[string]*node{}}
n.node[sec] = subNode
}
subNode.add(secs[:length-1])
}
}
func (n *Node) Match(item string) bool {
if n == nil {
return false
}
n.RLock()
defer n.RUnlock()
return n.matchSecs(strings.Split(n.trim(item), n.sep), false)
}
func (n *node) matchSecs(secs []string, fuzzNode bool) bool {
length := len(secs)
if length == 0 {
if _, ok := n.node[""]; ok {
return true
}
if _, ok := n.node["**"]; ok {
return true
}
if _, ok := n.node["*"]; ok {
return !fuzzNode
}
return false
}
if n, ok := n.node[secs[length-1]]; ok {
if n.matchSecs(secs[:length-1], false) {
return true
}
}
if n, ok := n.node["*"]; ok {
if n.matchSecs(secs[:length-1], true) {
return true
}
}
if _, ok := n.node["**"]; ok {
return true
}
return false
}
+81
View File
@@ -0,0 +1,81 @@
package util_test
import (
"testing"
"github.com/wweir/sower/util"
)
func TestNode_Match(t *testing.T) {
type test struct {
arg string
want bool
}
tests := []struct {
name string
node *util.Node
tests []test
}{{
"simple",
util.NewNodeFromRules("a.wweir.cc", "b.wweir.cc"),
[]test{
{"a.wweir.cc", true},
{"b.wweir.cc", true},
},
}, {
"parent",
util.NewNodeFromRules("wweir.cc", "a.wweir.cc"),
[]test{
{"wweir.cc", true},
{"a.wweir.cc", true},
{"b.wweir.cc", false},
},
}, {
"fuzz1",
util.NewNodeFromRules("wweir.cc", "a.wweir.cc", "*.wweir.cc"),
[]test{
{"wweir.cc", true},
{"a.wweir.cc", true},
{"b.wweir.cc", true},
{"a.b.wweir.cc", false},
},
}, {
"fuzz2",
util.NewNodeFromRules("a.*.cc", "c.wweir.*"),
[]test{
{"wweir.cc", false},
{"a.wweir.cc", true},
{"b.wweir.cc", false},
{"c.wweir.cc", true},
},
}, {
"fuzz3",
util.NewNodeFromRules("*.*.cc", "iamp.*.*"),
[]test{
{"wweir.cc", false},
{"a.wweir.cc", true},
{"b.wweir.cc", true},
{"iamp.wweir.cc", true},
},
}, {
"fuzz4",
util.NewNodeFromRules("**.cc", "a.**.com", "**.wweir.*"),
[]test{
{"wweir.cc", true},
{"a.wweir.cc", true},
{"a.b.wweir.cc", true},
{"a.fuzz.com", true},
{"b.fuzz.com", false},
{"www.wweir.com", true},
},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
for _, test := range tt.tests {
if got := tt.node.Match(test.arg); got != test.want {
t.Errorf("Node.Match(%s) = %v, want %v", test.arg, got, test.want)
}
}
})
}
}
+21 -8
View File
@@ -1,23 +1,28 @@
package parse
package util
import (
"io"
"net"
)
type TeeConn struct {
net.Conn
buf []byte
offset int
tee bool // read
buf []byte
offset int
stop bool // read
EnableWrite bool
}
func (t *TeeConn) StartOrReset() {
func (t *TeeConn) Reread() {
t.offset = 0
}
func (t *TeeConn) Reset() {
t.buf = []byte{}
t.offset = 0
t.tee = true
}
func (t *TeeConn) Stop() {
t.offset = 0
t.tee = false
t.stop = true
}
func (t *TeeConn) Read(b []byte) (n int, err error) {
@@ -29,9 +34,17 @@ func (t *TeeConn) Read(b []byte) (n int, err error) {
}
n, err = t.Conn.Read(b)
if t.tee {
if !t.stop {
t.buf = append(t.buf, b[:n]...)
t.offset += n
}
return n, err
}
func (t *TeeConn) Write(b []byte) (n int, err error) {
if t.stop || t.EnableWrite {
return t.Conn.Write(b)
}
return 0, io.EOF
}
+11
View File
@@ -0,0 +1,11 @@
package util
import "net"
func WithDefaultPort(addr string, port string) (address, host string) {
host, _, err := net.SplitHostPort(addr)
if err != nil {
return net.JoinHostPort(addr, port), addr
}
return addr, host
}