Compare commits

...
38 Commits
Author SHA1 Message Date
wweir a28a211d2d change ping logix 2021-08-15 13:45:28 +08:00
wweir 28696acafb format readme 2021-08-14 20:45:45 +08:00
wweir 38c7391ceb 1. add comments, 2. optimize text message 2021-08-11 06:48:18 +08:00
wweir 65234b1666 migrate deferlog to the separate package 2021-08-09 23:49:51 +08:00
wweir adca6606d2 remove unused code 2021-08-09 22:53:58 +08:00
wweir cfc1afb710 change architecture diagram 2021-08-09 08:30:38 +08:00
wweir 397825556c update the document 2021-08-09 06:53:44 +08:00
wweir 8694ff6edd fix for socks5 IP proxy 2021-08-07 05:59:16 +08:00
wweir a38affd97b fix panic in invalid socks5 case 2021-08-02 14:18:51 +08:00
wweir 74202aad83 optimize sowerd code 2021-07-30 19:13:59 +08:00
wweir 59dfa934a2 optimize code, comment / log / flag 2021-07-29 19:06:34 +08:00
wweir 8bfb26ba3f support use socks5 as upstream proxy 2021-07-29 16:19:55 +08:00
wweir fbc6c93395 extend sower protocol checksum length 2021-07-29 08:51:46 +08:00
wweir 2d269468ea add dns cache for dns proxy 2021-07-29 07:14:49 +08:00
wweir d7c0ba112e fix DNS forword issue, inspired by skydns 2021-07-28 14:59:48 +08:00
wweir d31ba7ef79 add deferlog package 2021-07-28 12:03:31 +08:00
wweir 8d32bca742 change dns solution config 2021-07-21 17:51:48 +08:00
wweir 426799a961 optimize config and log 2021-07-20 15:17:57 +08:00
wweir 5cf8df2a7a change binary name 2021-07-19 16:02:26 +08:00
wweir 7d024f3bfe support rule based config 2021-07-18 07:58:27 +08:00
wweir 585cc01744 update depency 2021-07-18 00:48:39 +08:00
wweir 8307128844 refactor, start v0.8 2021-07-17 08:05:02 +08:00
wweir dab1a78bbf mirate to trojan protocol 2020-07-31 13:51:51 +08:00
wweir 13a6be47a4 fix windows and daewin support 2020-07-13 13:45:33 +08:00
wweir 56b8e35fe7 support config reference 2020-07-13 07:24:40 +08:00
wweir 47b8fc601f Migrate to the MIT lisence 2020-06-14 06:48:34 +08:00
wweir 2dc128d1c8 Listen to single IP 2020-06-11 07:43:24 +08:00
wweir 2a4776dc28 Add dns cache support 2020-05-28 16:37:48 +08:00
wweir d121427bbb Support set system DNS automaticly 2020-04-19 08:34:24 +08:00
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
52 changed files with 2121 additions and 2150 deletions
-17
View File
@@ -1,17 +0,0 @@
# Compile
FROM golang:1.13-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" ]
-53
View File
@@ -1,53 +0,0 @@
name: Go
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 }}
+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
- uses: actions/checkout@v2
- name: test and build
run: |
make
+48
View File
@@ -0,0 +1,48 @@
name: Release
on:
push:
tags:
- "v*.*.*"
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v2
with:
go-version: ^1.16.7
- uses: actions/checkout@v2
- name: build matrix
run: |
make build GO='GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go'
tar czvf sower-linux-amd64.tar.gz sower sowerd
rm -f sower sowerd
make build GO='GOOS=linux GOARCH=arm CGO_ENABLED=0 go'
tar czvf sower-linux-arm.tar.gz sower sowerd
rm -f sower sowerd
make build GO='GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go'
tar czvf sower-linux-arm64.tar.gz sower sowerd
rm -f sower sowerd
make build GO='GOOS=linux GOARCH=mips CGO_ENABLED=0 go'
tar czvf sower-linux-mips.tar.gz sower sowerd
rm -f sower sowerd
make build GO='GOOS=linux GOARCH=mipsle CGO_ENABLED=0 go'
tar czvf sower-linux-mipsle.tar.gz sower sowerd
rm -f sower sowerd
make build GO='GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go'
tar czvf sower-darwin-amd64.tar.gz sower sowerd
rm -f sower sowerd
make build GO='GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 go'
tar czvf sower-darwin-arm64.tar.gz sower sowerd
rm -f sower sowerd
make build GO='GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go'
tar czvf sower-windows-amd64.tar.gz sower sowerd
rm -f sower sowerd
- uses: ncipollo/release-action@v1
with:
artifacts: "*.tar.gz"
token: ${{ secrets.GITHUB_TOKEN }}
+13 -3
View File
@@ -17,6 +17,16 @@
*.swo
.vscode
.idea
sower
sower.exe*
/sower.toml
**.env
**.mmdb
/cmd/client/client
/cmd/server/server
/sower
/sowerd
/*.yml
/*.yaml
/*.toml
/*.hcl
/*.tf
+17 -368
View File
@@ -1,373 +1,22 @@
Mozilla Public License Version 2.0
==================================
MIT License
1. Definitions
--------------
Copyright (c) 2020 wweir
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
1.3. "Contribution"
means Covered Software of a particular Contributor.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.
+18 -8
View File
@@ -1,12 +1,22 @@
MAKEFLAGS += --jobs all
GO:=CGO_ENABLED=0 go
default: test build
test:
go vet ./...
go list ./... | grep -v internal | xargs go test
build:
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 .
${GO} vet ./...
${GO} test ./...
build: sower sowerd
.PHONY: sower
sower:
${GO} build -ldflags "\
-X main.version=$(shell git describe --tags --always) \
-X main.date=$(shell date +%Y-%m-%d)" \
-o sower ./cmd/sower
.PHONY: sowerd
sowerd:
${GO} build -ldflags "\
-X main.version=$(shell git describe --tags --always) \
-X main.date=$(shell date +%Y-%m-%d)" \
-o sowerd ./cmd/sowerd
+44 -62
View File
@@ -1,89 +1,71 @@
# sower
[![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)
中文介绍见 [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.
The sower is a cross-platform intelligent transparent proxy tool. It provide both socks5 proxy and DNS-based proxy. All these kinds of proxy support intelligent router.
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'`
To enjoy the sower, you need to deploy sower on both server-side(sowerd) and client-side(sower).
## Server
*If you already have another proxy solution with socks5h support, you can skip server-side.*
## Sowerd
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.
_If you wanna use sower as secondary proxy to provide intelligent router, you can skip sowerd._
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
```
At the server-side, the sowerd runs just like a web server proxy. It will occupy two ports `80` / `443`.
## 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.
You can use your own certificate or the certificate automatically applied for by the sowerd from [`Let's Encrypt`](https://letsencrypt.org/).
There are 3 kinds of proxy solutions, they are HTTP(S)_PROXY / DNS-based proxy / port-forward.
There are two ways to run the sowerd service:
### HTTP(S)_PROXY
An HTTP(S)_PROXY listening on `:8080` is set by default if you run sower as client mode.
1. run the shell command with root permission
### DNS-based proxy
**DNS-based** solution is not recommanded now, for incompatible with TLS 1.3 esni extention.
```shell
# sowerd -password XXX -fake_site 127.0.0.1:8080
```
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.
2. install as a systemd service
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.
```service
[Unit]
Description=sower server service
After=network.target
### 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"
```
[Service]
Type=simple
ExecStart=/usr/local/bin/sowerd
Environment="FAKE_SITE=127.0.0.1:8080"
Environment="PASSWORD=XXX"
[Install]
WantedBy=multi-user.target
```
## Sower
A config file is required in sower client side. [Here](https://github.com/wweir/sower/wiki/sower.hcl) is an usable example in China.
`Sower` will take 4 port by default with root permission. They are: `udp(53)` / `tcp(80)` / `tcp(443)` / `tcp(1080)`.
After do the next three step, you can enjoy the intelligent transparent proxy solution:
1. run the command line with root permission:
```shell
# sower -f sower.hcl
```
2. changing your DNS server to `127.0.0.1` and
3. setting your proxy to `socks5h://127.0.0.1:1080`.
## 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
```
![Architecture diagram](./sower.drawio.svg)
+240
View File
@@ -0,0 +1,240 @@
package main
import (
"bufio"
"io"
"net"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"time"
"github.com/cristalhq/aconfig"
"github.com/cristalhq/aconfig/aconfighcl"
"github.com/cristalhq/aconfig/aconfigtoml"
"github.com/cristalhq/aconfig/aconfigyaml"
"github.com/miekg/dns"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
"github.com/wweir/sower/router"
)
var (
version, date string
conf = struct {
Remote struct {
Type string `default:"sower" required:"true" usage:"option: sower/trojan/socks5"`
Addr string `required:"true" usage:"proxy address, eg: proxy.com/127.0.0.1:7890"`
Password string `usage:"remote proxy password"`
}
DNS struct {
Disable bool `default:"false" usage:"disable DNS proxy"`
Serve string `default:"127.0.0.1" required:"true" usage:"dns server ip"`
Fallback string `default:"223.5.5.5" usage:"fallback dns server"`
}
Socks5 struct {
Disable bool `default:"false" usage:"disable sock5 proxy"`
Addr string `default:":1080" usage:"socks5 listen address"`
} `flag:"socks5"`
Router struct {
Block struct {
File string `usage:"block list file, local file or remote"`
FilePrefix string `default:"**." usage:"parsed as '<prefix>line_text'"`
Rules []string `usage:"block list rules"`
}
Direct struct {
File string `usage:"direct list file, local file or remote"`
FilePrefix string `default:"**." usage:"parsed as '<prefix>line_text'"`
Rules []string `usage:"direct list rules"`
}
Proxy struct {
File string `usage:"proxy list file, local file or remote"`
FilePrefix string `default:"**." usage:"parsed as '<prefix>line_text'"`
Rules []string `usage:"proxy list rules"`
}
Country struct {
MMDB string `usage:"mmdb file"`
File string `usage:"CIDR block list file, local file or remote"`
FilePrefix string `default:"" usage:"parsed as '<prefix>line_text'"`
Rules []string `usage:"CIDR list rules"`
}
}
}{}
)
func init() {
if err := aconfig.LoaderFor(&conf, aconfig.Config{
AllowUnknownFields: true,
FileFlag: "f",
FileDecoders: map[string]aconfig.FileDecoder{
".yml": aconfigyaml.New(),
".yaml": aconfigyaml.New(),
".toml": aconfigtoml.New(),
".hcl": aconfighcl.New(),
},
}).Load(); err != nil {
log.Fatal().Err(err).
Interface("config", conf).
Msg("Load config")
}
conf.Router.Direct.Rules = append(conf.Router.Direct.Rules,
conf.Remote.Addr, "**.in-addr.arpa", "**.ip6.arpa")
log.Info().
Str("version", version).
Str("date", date).
Interface("config", conf).
Msg("Starting")
}
func main() {
proxtDial := GenProxyDial(conf.Remote.Type, conf.Remote.Addr, conf.Remote.Password)
r := router.NewRouter(conf.DNS.Serve, conf.DNS.Fallback, conf.Router.Country.MMDB, proxtDial)
r.SetRules(conf.Router.Block.Rules, conf.Router.Direct.Rules, conf.Router.Proxy.Rules,
conf.Router.Country.Rules)
go func() {
if conf.DNS.Disable {
log.Info().Msg("DNS proxy disabled")
return
}
lnHTTP, err := net.Listen("tcp", net.JoinHostPort(conf.DNS.Serve, "80"))
if err != nil {
log.Fatal().Err(err).Msg("listen port")
}
go ServeHTTP(lnHTTP, r)
lnHTTPS, err := net.Listen("tcp", net.JoinHostPort(conf.DNS.Serve, "443"))
if err != nil {
log.Fatal().Err(err).Msg("listen port")
}
go ServeHTTPS(lnHTTPS, r)
log.Info().
Str("listen_on", conf.DNS.Serve).
Msg("DNS proxy started")
if err := dns.ListenAndServe(net.JoinHostPort(conf.DNS.Serve, "53"), "udp", r); err != nil {
log.Fatal().Err(err).Msg("serve dns")
}
}()
go func() {
if conf.Socks5.Disable {
log.Info().Msg("SOCKS5 proxy disabled")
return
}
ln, err := net.Listen("tcp", conf.Socks5.Addr)
if err != nil {
log.Fatal().Err(err).Msg("listen port")
}
log.Info().Msgf("SOCKS5 proxy listening on %s", conf.Socks5.Addr)
go ServeSocks5(ln, r)
}()
start := time.Now()
conf.Router.Block.Rules = append(conf.Router.Block.Rules,
loadRules(proxtDial, conf.Router.Block.File, conf.Router.Block.FilePrefix)...)
conf.Router.Direct.Rules = append(conf.Router.Direct.Rules,
loadRules(proxtDial, conf.Router.Direct.File, conf.Router.Direct.FilePrefix)...)
conf.Router.Proxy.Rules = append(conf.Router.Proxy.Rules,
loadRules(proxtDial, conf.Router.Proxy.File, conf.Router.Proxy.FilePrefix)...)
conf.Router.Country.Rules = append(conf.Router.Country.Rules,
loadRules(proxtDial, conf.Router.Country.File, conf.Router.Country.FilePrefix)...)
r.SetRules(conf.Router.Block.Rules, conf.Router.Direct.Rules, conf.Router.Proxy.Rules,
conf.Router.Country.Rules)
log.Info().
Dur("spend", time.Since(start)).
Int("blockRule", len(conf.Router.Block.Rules)).
Int("directRule", len(conf.Router.Direct.Rules)).
Int("proxyRule", len(conf.Router.Proxy.Rules)).
Int("countryRule", len(conf.Router.Country.Rules)).
Msg("Loaded rules, proxy started")
select {}
}
func loadRules(proxyDial router.ProxyDialFn, file, linePrefix string) []string {
var loadFn func() (io.ReadCloser, error)
if _, err := url.Parse(file); err == nil {
// load rule file from remote by HTTP
client := &http.Client{
Transport: &http.Transport{
Dial: func(network, addr string) (net.Conn, error) {
domain, port, _ := net.SplitHostPort(addr)
p, _ := strconv.Atoi(port)
return proxyDial("tcp", domain, uint16(p))
},
},
}
loadFn = func() (io.ReadCloser, error) {
resp, err := client.Get(file)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
resp.Body.Close()
return nil, errors.Errorf("status code: %d", resp.StatusCode)
}
return resp.Body, nil
}
} else {
// load rule file from local file
loadFn = func() (io.ReadCloser, error) {
return os.Open(file)
}
}
// load rule file, retry 10 times
rc, err := loadFn()
for i := time.Duration(1); i < 10; i++ {
if err == nil {
break
}
// wait: 28.5s
time.Sleep(i * i * 100 * time.Millisecond)
rc, err = loadFn()
}
if err != nil {
log.Fatal().Err(err).
Str("file", file).
Msg("load config file")
}
defer rc.Close()
// parse rule file into rule tree
var lines []string
br := bufio.NewReader(rc)
for {
line, _, err := br.ReadLine()
if err == io.EOF {
break
} else if err != nil {
log.Error().Err(err).
Str("file", file).
Msg("read line")
return nil
}
if strings.TrimSpace(string(line)) == "" {
continue
}
// use line content as suffix
lines = append(lines, linePrefix+string(line))
}
return lines
}
+162
View File
@@ -0,0 +1,162 @@
package main
import (
"bufio"
"crypto/tls"
"net"
"net/http"
"time"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
"github.com/wweir/sower/pkg/teeconn"
"github.com/wweir/sower/router"
"github.com/wweir/sower/transport"
"github.com/wweir/sower/transport/socks5"
"github.com/wweir/sower/transport/sower"
"github.com/wweir/sower/transport/trojan"
"github.com/wweir/sower/util"
)
func GenProxyDial(proxyType, proxyHost, proxyPassword string) router.ProxyDialFn {
var proxy transport.Transport
var dialFn func() (net.Conn, error)
switch conf.Remote.Type {
case "sower":
proxy = sower.New(conf.Remote.Password)
tlsCfg := &tls.Config{}
dialFn = func() (net.Conn, error) {
return tls.Dial("tcp", net.JoinHostPort(proxyHost, "443"), tlsCfg)
}
case "trojan":
proxy = trojan.New(conf.Remote.Password)
tlsCfg := &tls.Config{}
dialFn = func() (net.Conn, error) {
return tls.Dial("tcp", net.JoinHostPort(proxyHost, "443"), tlsCfg)
}
case "socks5":
proxy = socks5.New()
dialFn = func() (net.Conn, error) {
return net.Dial("tcp", proxyHost)
}
default:
log.Fatal().
Str("type", conf.Remote.Type).
Msg("unknown proxy type")
}
return func(network, host string, port uint16) (net.Conn, error) {
if host == "" || port == 0 {
return nil, errors.Errorf("invalid addr(%s:%d)", host, port)
}
conn, err := dialFn()
if err != nil {
return nil, err
}
if err := proxy.Wrap(conn, host, port); err != nil {
conn.Close()
return nil, err
}
return conn, nil
}
}
func ServeHTTP(ln net.Listener, r *router.Router) {
conn, err := ln.Accept()
if err != nil {
log.Fatal().Err(err).
Msg("serve socks5")
}
go ServeHTTP(ln, r)
start := time.Now()
teeconn := teeconn.New(conn)
defer teeconn.Close()
req, err := http.ReadRequest(bufio.NewReader(teeconn))
if err != nil {
log.Error().Err(err).Msg("read http request")
return
}
rc, err := r.ProxyDial("tcp", req.Host, 80)
if err != nil {
log.Error().Err(err).
Str("host", req.Host).
Interface("req", req.URL).
Msg("dial proxy")
return
}
defer rc.Close()
teeconn.Stop().Reread()
util.Relay(teeconn, rc)
log.Debug().
Str("host", req.Host).
Dur("spend", time.Since(start)).
Msg("serve http")
}
func ServeHTTPS(ln net.Listener, r *router.Router) {
conn, err := ln.Accept()
if err != nil {
log.Fatal().Err(err).
Msg("serve socks5")
}
go ServeHTTPS(ln, r)
start := time.Now()
teeconn := teeconn.New(conn)
defer teeconn.Close()
var domain string
tls.Server(teeconn, &tls.Config{
GetConfigForClient: func(hello *tls.ClientHelloInfo) (*tls.Config, error) {
domain = hello.ServerName
return nil, nil
},
}).Handshake()
rc, err := r.ProxyDial("tcp", domain, 443)
if err != nil {
log.Error().Err(err).
Str("host", domain).
Msg("dial proxy")
return
}
defer rc.Close()
teeconn.Stop().Reread()
util.Relay(teeconn, rc)
log.Debug().
Str("host", domain).
Dur("spend", time.Since(start)).
Msg("serve http")
}
func ServeSocks5(ln net.Listener, r *router.Router) {
conn, err := ln.Accept()
if err != nil {
log.Fatal().Err(err).
Msg("serve socks5")
}
go ServeSocks5(ln, r)
defer conn.Close()
addr, err := socks5.New().Unwrap(conn)
if err != nil {
log.Warn().Err(err).
Msgf("parse socks5 target: %s", addr)
return
}
host, port := addr.(*socks5.AddrHead).Addr()
r.RouteHandle(conn, host, port)
}
+143
View File
@@ -0,0 +1,143 @@
package main
import (
"crypto/tls"
"net"
"net/http"
"os"
"path/filepath"
"time"
"github.com/cristalhq/aconfig"
"github.com/rs/zerolog/log"
"github.com/wweir/deferlog"
"github.com/wweir/sower/pkg/teeconn"
"github.com/wweir/sower/transport/sower"
"github.com/wweir/sower/transport/trojan"
"github.com/wweir/sower/util"
"golang.org/x/crypto/acme/autocert"
)
var (
version, date string
conf = struct {
ServeIP string `usage:"listen to port 80 443 of this IP, eg: 0.0.0.0"`
Password string `required:"true"`
FakeSite string `required:"true" default:"127.0.0.1:8080" usage:"fake site address"`
Cert struct {
Email string
Cert string
Key string
}
}{}
)
func init() {
err := aconfig.LoaderFor(&conf, aconfig.Config{}).Load()
deferlog.Std.InfoFatal(err).
Str("version", version).
Str("date", date).
Interface("config", conf).
Msg("Load config")
}
func main() {
cacheDir, _ := os.UserCacheDir()
cacheDir = filepath.Join(cacheDir, "sower")
if err := os.MkdirAll(cacheDir, 0600); err != nil {
log.Fatal().Err(err).
Str("dir", cacheDir).
Msg("make cache dir")
}
certManager := autocert.Manager{
Prompt: autocert.AcceptTOS,
Email: conf.Cert.Email,
Cache: autocert.DirCache(cacheDir),
}
tlsConf := &tls.Config{
GetCertificate: certManager.GetCertificate,
MinVersion: tls.VersionTLS12,
NextProtos: []string{"http/1.1", "h2"},
}
if conf.Cert.Cert != "" || conf.Cert.Key != "" {
cert, err := tls.LoadX509KeyPair(conf.Cert.Cert, conf.Cert.Key)
if err != nil {
log.Fatal().Err(err).Msg("load certificate")
}
tlsConf.GetCertificate = nil
tlsConf.Certificates = []tls.Certificate{cert}
}
// Redirect 80 to 443
go func() {
err := http.ListenAndServe(net.JoinHostPort(conf.ServeIP, "80"),
certManager.HTTPHandler(http.HandlerFunc(redirectToHTTPS)))
log.Fatal().Err(err).
Str("IP", conf.ServeIP).
Msg("Listen HTTP service")
}()
ln, err := tls.Listen("tcp", net.JoinHostPort(conf.ServeIP, "443"), tlsConf)
deferlog.Std.InfoFatal(err).
Str("IP", conf.ServeIP).
Msg("Start listen HTTPS service")
go serve443(ln, conf.FakeSite, sower.New(conf.Password), trojan.New(conf.Password))
select {}
}
func redirectToHTTPS(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)
}
func serve443(ln net.Listener, fakeSite string, sower *sower.Sower, trojan *trojan.Trojan) {
conn, err := ln.Accept()
if err != nil {
log.Fatal().Err(err).Msg("serve 443 port")
}
go serve443(ln, fakeSite, sower, trojan)
teeconn := teeconn.New(conn)
defer teeconn.Close()
var addr net.Addr
var dur time.Duration
defer func() {
deferlog.DebugWarn(err).
Dur("spend", dur).
Msgf("relay conn to %s", addr)
}()
// 1. detect if it's a sower underlaying connection
teeconn.Reread()
if addr, err = sower.Unwrap(teeconn); err == nil {
teeconn.Stop()
dur, err = util.RelayTo(teeconn, addr.String())
return
}
// 2. detect if it's a trojan underlaying connection
teeconn.Reread()
if addr, err = trojan.Unwrap(teeconn); err == nil {
teeconn.Stop()
dur, err = util.RelayTo(teeconn, addr.String())
return
}
// 3. fallback to fake site
teeconn.Stop().Reread()
dur, err = util.RelayTo(teeconn, fakeSite)
}
-139
View File
@@ -1,139 +0,0 @@
package conf
import (
"flag"
"os"
toml "github.com/pelletier/go-toml"
"github.com/wweir/utils/log"
)
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"`
Router struct {
DetectLevel int `toml:"detect_level"`
DetectTimeout string `toml:"detect_timeout"`
ProxyList []string `toml:"proxy_list"`
DirectList []string `toml:"direct_list"`
DynamicList []string `toml:"dynamic_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"`
}
var (
version, date string
Server = server{}
Client = client{}
Conf = struct {
file string
Password string `toml:"password"`
Server *server `toml:"server"`
Client *client `toml:"client"`
}{"", "", &Server, &Client}
flushCh = make(chan struct{})
installCmd string
uninstallFlag bool
)
func init() {
flag.StringVar(&Conf.Password, "password", "", "password")
flag.StringVar(&Server.Upstream, "s", "", "upstream http service, eg: 127.0.0.1:8080")
flag.StringVar(&Server.CertFile, "s_cert", "", "tls cert file, gen cert from letsencrypt if empty")
flag.StringVar(&Server.KeyFile, "s_key", "", "tls key file, gen cert from letsencrypt if empty")
flag.StringVar(&Client.Address, "c", "", "remote server domain, eg: aa.bb.cc, socks5h://127.0.0.1:1080")
flag.StringVar(&Client.HTTPProxy, "http_proxy", ":8080", "http proxy, empty to disable")
flag.IntVar(&Client.Router.DetectLevel, "level", 2, "dynamic rule detect level: 0~4")
flag.StringVar(&Client.Router.DetectTimeout, "timeout", "300ms", "dynamic rule detect timeout")
flag.BoolVar(&uninstallFlag, "uninstall", false, "uninstall service")
Init() // execute platform init logic
if !flag.Parsed() {
flag.Parse()
}
if uninstallFlag {
uninstall()
os.Exit(0)
}
if installCmd != "" {
install()
os.Exit(0)
}
defer log.Infow("starting", "config", &Conf)
if Conf.file == "" {
return
}
for i := range loadConfigFns {
if err := loadConfigFns[i].fn(); err != nil {
log.Fatalw("load config", "config", Conf.file, "step", loadConfigFns[i].step, "err", err)
}
}
go flushConfDaemon()
}
// 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
}
defer f.Close()
return toml.NewDecoder(f).Decode(&Conf)
}}}
func PersistRule(domain string) {
Client.Router.DynamicList = append(Client.Router.DynamicList, 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
}
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 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)
}
}
}
}
-72
View File
@@ -1,72 +0,0 @@
// +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>`
func Init() {
flag.StringVar(&Conf.file, "f", "", "config file, rewrite all other parameters if set")
flag.StringVar(&installCmd, "install", "", "install service with cmd, eg: '-f /etc/sower.toml'")
}
func install() {
execFile, err := filepath.Abs(os.Args[0])
if err != nil {
log.Fatalw("get binary path", "err", err)
}
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 -w " + 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
}
-72
View File
@@ -1,72 +0,0 @@
// +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`
func Init() {
flag.StringVar(&Conf.file, "f", "", "config file, rewrite all other parameters if set")
flag.StringVar(&installCmd, "install", "", "install service with cmd, eg: '-f /etc/sower.toml'")
}
func install() {
execFile, err := filepath.Abs(os.Args[0])
if err != nil {
log.Fatalw("get binary path", "err", err)
}
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
}
-173
View File
@@ -1,173 +0,0 @@
// +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 execFile, _ = filepath.Abs(os.Args[0])
var execDir, _ = filepath.Abs(filepath.Dir(execFile))
func Init() {
flag.StringVar(&Conf.file, "f", filepath.Join(execDir, "sower.toml"), "config file, rewrite all other parameters if set")
flag.StringVar(&installCmd, "install", "", "install service with cmd")
flag.Parse()
switch {
case installCmd != "":
case uninstallFlag:
default:
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
}
-55
View File
@@ -1,55 +0,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 = 2 # 0~4, the bigger the harder to add
detect_timeout = "300ms"
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
+12 -7
View File
@@ -1,14 +1,19 @@
module github.com/wweir/sower
go 1.13
go 1.16
require (
github.com/cristalhq/aconfig v0.16.2
github.com/cristalhq/aconfig/aconfighcl v0.16.1
github.com/cristalhq/aconfig/aconfigtoml v0.16.1
github.com/cristalhq/aconfig/aconfigyaml v0.16.1
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/libp2p/go-reuseport v0.0.2
github.com/miekg/dns v1.1.43
github.com/oschwald/geoip2-golang v1.5.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
github.com/rs/zerolog v1.23.0
github.com/ulule/deepcopier v0.0.0-20200430083143-45decc6639b6
github.com/wweir/deferlog v0.0.1
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
)
+61 -76
View File
@@ -1,102 +1,87 @@
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cristalhq/aconfig v0.16.1/go.mod h1:NXaRp+1e6bkO4dJn+wZ71xyaihMDYPtCSvEhMTm/H3E=
github.com/cristalhq/aconfig v0.16.2 h1:cN+y3rtHyK9ar7NRiY4E+USs07P2qnZnGeZfNzXoV2g=
github.com/cristalhq/aconfig v0.16.2/go.mod h1:NXaRp+1e6bkO4dJn+wZ71xyaihMDYPtCSvEhMTm/H3E=
github.com/cristalhq/aconfig/aconfighcl v0.16.1 h1:RiziovFKOwB6+H1LCD7DXxrFgoKDEJxxYLSsAOsj+vQ=
github.com/cristalhq/aconfig/aconfighcl v0.16.1/go.mod h1:YvZyVGtty/7yIdyOPPl68u7ENaUMq6ybZAph+n3g4II=
github.com/cristalhq/aconfig/aconfigtoml v0.16.1 h1:q9DJfJqdLvZj7vL3bOIR6QM0ySpA0i6X8CZ8FNob6nc=
github.com/cristalhq/aconfig/aconfigtoml v0.16.1/go.mod h1:VfRDnJBq09TZXiQdnnd4CBsNyj4s7AEDJlCxDSDt5DI=
github.com/cristalhq/aconfig/aconfigyaml v0.16.1 h1:ghmzWFolCuja6BzBvHkNAn2Qsewu6xqdAXQyWnXjvZw=
github.com/cristalhq/aconfig/aconfigyaml v0.16.1/go.mod h1:UgM0LkO4TKbC0s/oUP2p5POM6QiQwNvOFXostf6con0=
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/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
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/libp2p/go-reuseport v0.0.2 h1:XSG94b1FJfGA01BUrT82imejHQyTxO4jEWqheyCXYvU=
github.com/libp2p/go-reuseport v0.0.2/go.mod h1:SPD+5RwGC7rcnzngoYC86GjPzjSywuQyMVAheVBD9nQ=
github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
github.com/oschwald/geoip2-golang v1.5.0 h1:igg2yQIrrcRccB1ytFXqBfOHCjXWIoMv85lVJ1ONZzw=
github.com/oschwald/geoip2-golang v1.5.0/go.mod h1:xdvYt5xQzB8ORWFqPnqMwZpCpgNagttWdoZLlJQzg7s=
github.com/oschwald/maxminddb-golang v1.8.0 h1:Uh/DSnGoxsyp/KYbY1AuP0tYEwfs0sCph9p/UMXK/Hk=
github.com/oschwald/maxminddb-golang v1.8.0/go.mod h1:RXZtst0N6+FY/3qCNmZMBApR19cdQj43/NM9VkrNAis=
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/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.23.0 h1:UskrK+saS9P9Y789yNNulYKdARjPZuS35B8gJF2x60g=
github.com/rs/zerolog v1.23.0/go.mod h1:6c7hFfxPOy7TacJc4Fcdi24/J0NKYGzjG8FWRI916Qo=
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=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/ulule/deepcopier v0.0.0-20200430083143-45decc6639b6 h1:TtyC78WMafNW8QFfv3TeP3yWNDG+uxNkk9vOrnDu6JA=
github.com/ulule/deepcopier v0.0.0-20200430083143-45decc6639b6/go.mod h1:h8272+G2omSmi30fBXiZDMkmHuOgonplfKIKjQWzlfs=
github.com/wweir/deferlog v0.0.1 h1:KKWFRsS46KTqkAR4F79VtZ4h7Q6PvnbrPkBdUxEUujc=
github.com/wweir/deferlog v0.0.1/go.mod h1:mIpuA0PTdQ28oas0iR1uZYlFiDyOxMOXQgmx6gNOg3I=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
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/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
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/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
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/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
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/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
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=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
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=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-46
View File
@@ -1,46 +0,0 @@
package main
import (
"flag"
"fmt"
"github.com/wweir/sower/conf"
"github.com/wweir/sower/proxy"
"github.com/wweir/sower/router"
)
func main() {
switch {
case conf.Server.Upstream != "":
proxy.StartServer(conf.Server.Upstream, conf.Conf.Password,
conf.Server.CertFile, conf.Server.KeyFile, conf.Server.CertEmail)
case conf.Client.Address != "":
route := &router.Route{
ProxyAddress: conf.Client.Address,
ProxyPassword: conf.Conf.Password,
DetectLevel: conf.Client.Router.DetectLevel,
DetectTimeout: conf.Client.Router.DetectTimeout,
DirectList: conf.Client.Router.DirectList,
ProxyList: conf.Client.Router.ProxyList,
DynamicList: conf.Client.Router.DynamicList,
}
go proxy.StartHTTPProxy(conf.Client.HTTPProxy, conf.Client.Address,
[]byte(conf.Conf.Password), route.ShouldProxy)
if conf.Client.DNSServeIP != "" {
go proxy.StartDNS(conf.Client.DNSServeIP, conf.Client.DNSUpstream,
route.ShouldProxy)
}
proxy.StartClient(conf.Client.Address, conf.Conf.Password,
conf.Client.DNSServeIP != "", conf.Client.PortForward)
default:
if conf.Server.Upstream == "" && conf.Client.Address == "" {
fmt.Println()
flag.Usage()
}
}
}
+1 -1
View File
@@ -14,7 +14,7 @@ import (
var xid = make([]byte, 4)
var broadcastAddr, _ = net.ResolveUDPAddr("udp", "255.255.255.255:67")
func GetDefaultDNSServer() (string, error) {
func GetDNSServer() (string, error) {
iface, err := PickInternetInterface()
if err != nil {
return "", errors.Wrap(err, "pick interface")
@@ -3,11 +3,11 @@ package dhcp_test
import (
"fmt"
"github.com/wweir/sower/proxy/dhcp"
"github.com/wweir/sower/pkg/dhcp"
)
func Example_dns() {
got, err := dhcp.GetDefaultDNSServer()
got, err := dhcp.GetDNSServer()
if err != nil {
panic(err)
}
@@ -3,7 +3,7 @@ package dhcp_test
import (
"fmt"
"github.com/wweir/sower/proxy/dhcp"
"github.com/wweir/sower/pkg/dhcp"
)
func Example_iface() {
+139
View File
@@ -0,0 +1,139 @@
package mem
import (
"errors"
"reflect"
"sync"
"time"
"github.com/ulule/deepcopier"
)
// Data define the type which can speed up by mem cache
type Data interface {
Fulfill(key string) (err error)
}
// Cache is the definition of cache, be careful of the memory usage
type Cache struct {
old *sync.Map
now *sync.Map
barrier *sync.Map
rotate <-chan time.Time
rwmutex *sync.RWMutex
}
// DefaultCache is default cache for surge
var DefaultCache = New(time.Minute)
// Remember is a surge, it provides a quite simple way to use cache
func Remember(dst Data, key string) error {
return DefaultCache.Remember(dst, key)
}
// Delete is a surge, it delete a specified data in DefaultCache
func Delete(dst Data, key string) {
DefaultCache.Delete(dst, key)
}
// New create a cache entity with a custom expiration time
func New(rotateInterval time.Duration) *Cache {
return &Cache{
old: &sync.Map{},
now: &sync.Map{},
barrier: &sync.Map{},
rotate: time.NewTicker(rotateInterval).C,
rwmutex: &sync.RWMutex{},
}
}
// Remember automatically save and retrieve data from a cache entity
func (c *Cache) Remember(dst Data, key string) error {
rv := reflect.ValueOf(dst)
if rv.Kind() != reflect.Ptr {
panic("invalid not pointor type: " + reflect.TypeOf(dst).Name())
} else if rv.IsNil() {
return errors.New("invalid nil pointor")
}
c.rwmutex.RLock()
defer c.rwmutex.RUnlock()
// rotate logic, rwlock just protect fields in Cache, but not field content.
// So that, write lock just take a very short time, and simple read lock is
// just an atomic action, do not care the performance
select {
case <-c.rotate:
c.old = c.now
c.now = &sync.Map{}
c.barrier = &sync.Map{}
default:
}
// First: load from cache
if val, ok := c.now.Load(key); ok {
return deepcopier.Copy(val).To(dst)
}
// Second: load from old cache, or waitting the sigle groutine getting data
ch := make(chan struct{})
if chVal, ok := c.barrier.LoadOrStore(key, ch); ok {
close(ch) // the ch is not used
if val, ok := c.old.Load(key); ok {
return deepcopier.Copy(val).To(dst)
}
// type chan: wait the sigle groutine getting data
// type error: already failed
if ch, ok = chVal.(chan struct{}); ok {
<-ch
if val, ok := c.now.Load(key); ok {
return deepcopier.Copy(val).To(dst)
}
}
val, _ := c.barrier.Load(key)
if err, ok := val.(error); ok {
return err
}
panic("new value lost, please report a bug")
}
// Third: getting data from CacheType, maybe from db
err := dst.Fulfill(key)
if err != nil {
c.barrier.Store(key, err)
return err
}
c.now.Store(key, dst)
close(ch) // broadcast, wakeup all waiting groutine
return nil
}
// Delete immediately specified the cached content to expire
func (c *Cache) Delete(dst Data, key string) {
c.rwmutex.Lock()
defer c.rwmutex.Unlock()
c.old.Delete(key)
c.now.Delete(key)
c.barrier.Store(key, errors.New(key+" is deleted"))
}
// Rotate force refresh cached data
func (c *Cache) Rotate(reset bool) {
c.rwmutex.Lock()
defer c.rwmutex.Unlock()
if reset {
c.old = &sync.Map{}
} else {
c.old = c.now
}
c.now = &sync.Map{}
c.barrier = &sync.Map{}
}
+56
View File
@@ -0,0 +1,56 @@
package teeconn
import (
"io"
"net"
)
type Conn struct {
net.Conn
buf []byte
offset int
stop bool // read
err error
}
func New(c net.Conn) *Conn {
return &Conn{Conn: c}
}
func (t *Conn) Reread() {
t.offset = 0
}
func (t *Conn) Reset() {
t.buf = []byte{}
t.offset = 0
}
func (t *Conn) Stop() *Conn {
t.stop = true
return t
}
func (t *Conn) Read(b []byte) (n int, err error) {
length := len(t.buf) - t.offset
if length > 0 {
n = copy(b, t.buf[t.offset:])
t.offset += n
return n, t.err
}
n, t.err = t.Conn.Read(b)
if !t.stop {
t.buf = append(t.buf, b[:n]...)
t.offset += n
}
return n, t.err
}
func (t *Conn) Write(b []byte) (n int, err error) {
if t.stop {
return t.Conn.Write(b)
}
return 0, io.ErrShortWrite
}
-92
View File
@@ -1,92 +0,0 @@
package proxy
import (
"net"
"strings"
"time"
"github.com/miekg/dns"
"github.com/wweir/sower/proxy/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("detect 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
}
-93
View File
@@ -1,93 +0,0 @@
package proxy
import (
"context"
"crypto/tls"
"io"
"net"
"net/http"
"time"
"github.com/wweir/sower/transport"
"github.com/wweir/utils/log"
)
func StartHTTPProxy(httpProxyAddr, serverAddr string, password []byte, shouldProxy func(string) bool) {
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 {
httpProxy(w, r, serverAddr, password, shouldProxy)
}
}),
// Disable HTTP/2.
TLSNextProto: map[string]func(*http.Server, *tls.Conn, http.Handler){},
IdleTimeout: 90 * time.Second,
}
go log.Fatalw("serve http proxy", "addr", httpProxyAddr, "err", srv.ListenAndServe())
}
func httpProxy(w http.ResponseWriter, r *http.Request,
serverAddr string, password []byte, shouldProxy func(string) bool) {
target, host := withDefaultPort(r.Host, "80")
roundTripper := &http.Transport{}
if shouldProxy(host) {
roundTripper.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
return transport.Dial(serverAddr, target, password)
}
}
resp, err := roundTripper.RoundTrip(r)
if err != nil {
http.Error(w, err.Error(), http.StatusServiceUnavailable)
return
}
defer resp.Body.Close()
for k, vs := range resp.Header {
for _, v := range vs {
w.Header().Add(k, v)
}
}
w.WriteHeader(resp.StatusCode)
io.Copy(w, resp.Body)
}
func httpsProxy(w http.ResponseWriter, r *http.Request,
serverAddr string, password []byte, shouldProxy func(string) bool) {
target, host := withDefaultPort(r.Host, "443")
conn, _, err := w.(http.Hijacker).Hijack()
if err != nil {
http.Error(w, err.Error(), http.StatusServiceUnavailable)
return
}
conn.(*net.TCPConn).SetKeepAlive(true)
if _, err := conn.Write([]byte(r.Proto + " 200 Connection established\r\n\r\n")); err != nil {
http.Error(w, err.Error(), http.StatusServiceUnavailable)
conn.Close()
return
}
var rc net.Conn
if shouldProxy(host) {
rc, err = transport.Dial(serverAddr, target, password)
} else {
rc, err = net.Dial("tcp", target)
}
if err != nil {
conn.Write([]byte("sower dial " + serverAddr + " fail: " + err.Error()))
conn.Close()
return
}
defer rc.Close()
relay(conn, rc)
}
-41
View File
@@ -1,41 +0,0 @@
package proxy
import (
"bufio"
"crypto/tls"
"net"
"net/http"
"github.com/wweir/sower/util"
)
func ParseHTTP(conn net.Conn) (net.Conn, string, error) {
teeConn := &util.TeeConn{Conn: conn}
defer teeConn.Stop()
resp, err := http.ReadRequest(bufio.NewReader(teeConn))
if err != nil {
return teeConn, "", err
}
if _, _, err := net.SplitHostPort(resp.Host); err != nil {
resp.Host = net.JoinHostPort(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()
return teeConn, net.JoinHostPort(domain, "443"), nil
}
-135
View File
@@ -1,135 +0,0 @@
package proxy
import (
"crypto/tls"
"net"
"net/http"
"os"
"path/filepath"
"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) {
passwordData := []byte(password)
relayToRemote := func(lnAddr, target string,
parseFn func(net.Conn) (net.Conn, string, error)) {
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(serverAddr, target, passwordData)
if err != nil {
log.Warnw("dial", "addr", serverAddr, "err", err)
return
}
defer rc.Close()
relay(conn, rc)
}(conn)
}
}
for from, to := range forwards {
go relayToRemote(from, to, nil)
}
if enableDNS {
go relayToRemote(":80", "", ParseHTTP)
go relayToRemote(":443", "", ParseHTTPS)
}
select {}
}
func StartServer(relayTarget, password, certFile, keyFile, email string) {
dir, _ := os.UserCacheDir()
dir = filepath.Join("/", dir, "sower")
log.Infow("certificate cache dir", "dir", dir)
certManager := autocert.Manager{
Prompt: autocert.AcceptTOS,
Email: email,
Cache: autocert.DirCache(dir),
}
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) {
if host, _, err := net.SplitHostPort(r.Host); err != nil {
r.URL.Host = r.Host
} else {
r.URL.Host = host
}
r.URL.Scheme = "https"
http.Redirect(w, r, r.URL.String(), 301)
})))
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.ToProxyConn(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)
}
}
-38
View File
@@ -1,38 +0,0 @@
package proxy
import (
"io"
"net"
"sync"
"sync/atomic"
"time"
)
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
}
func relay(conn1, conn2 net.Conn) {
wg := &sync.WaitGroup{}
exitFlag := new(int32)
wg.Add(2)
go redirect(conn2, conn1, wg, exitFlag)
redirect(conn1, conn2, wg, exitFlag)
wg.Wait()
}
func redirect(dst, src net.Conn, wg *sync.WaitGroup, exitFlag *int32) {
io.Copy(dst, src)
if atomic.CompareAndSwapInt32(exitFlag, 0, 1) {
// wakeup blocked goroutine
now := time.Now()
src.SetDeadline(now)
dst.SetDeadline(now)
}
wg.Done()
}
+54
View File
@@ -0,0 +1,54 @@
package router
import (
"context"
"net"
"time"
"github.com/rs/zerolog/log"
)
func (r *Router) localSite(domain string) bool {
// parse domain to IP
ip := net.ParseIP(domain)
if ip == nil {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
ips, err := net.DefaultResolver.LookupIP(ctx, "ip", domain)
if err != nil || len(ips) == 0 {
log.Warn().Err(err).
Str("domain", domain).
Int("ips", len(ips)).
Msg("resolve domain")
return false
}
ip = ips[0]
}
// CIDR match
for _, cidr := range r.country.cidrs {
if cidr.Contains(ip) {
return true
}
}
// MMDB match CN
if r.country.Reader != nil {
city, err := r.country.City(ip)
if err != nil {
log.Warn().Err(err).
Str("domain", domain).
IPAddr("ip", ip).
Msg("mmdb search")
return false
}
if city.Country.IsoCode == "CN" {
return true
}
}
return false
}
+93
View File
@@ -0,0 +1,93 @@
package router
import (
"net"
"time"
"github.com/miekg/dns"
"github.com/wweir/deferlog"
)
func (r *Router) ServeDNS(w dns.ResponseWriter, req *dns.Msg) {
// https://stackoverflow.com/questions/4082081/requesting-a-and-aaaa-records-in-single-dns-query/4083071#4083071
if len(req.Question) == 0 {
_ = w.WriteMsg(r.dnsFail(req, dns.RcodeFormatError))
return
}
domain := req.Question[0].Name
// 1. rule_based( block > direct > proxy )
switch {
case r.blockRule.Match(domain):
_ = w.WriteMsg(r.dnsFail(req, dns.RcodeNameError))
return
case r.directRule.Match(domain):
case r.proxyRule.Match(domain):
_ = w.WriteMsg(r.dnsProxyA(domain, r.dns.serveIP, req))
return
}
// 2. direct with cache, do not fallback to proxy to avoid side-effect
c := &dnsCache{Router: r, Req: req}
if err := r.dns.cache.Remember(c, req.Question[0].String()); err != nil {
_ = w.WriteMsg(r.dnsFail(req, dns.RcodeServerFailure))
return
}
c.Resp.SetReply(req)
c.Resp.Compress = true
_ = w.WriteMsg(c.Resp)
}
func (r *Router) dnsFail(req *dns.Msg, rcode int) *dns.Msg {
m := new(dns.Msg)
m.SetRcode(req, dns.RcodeServerFailure)
return m
}
func (r *Router) dnsProxyA(domain string, localIP net.IP, req *dns.Msg) *dns.Msg {
m := new(dns.Msg)
m.SetReply(req)
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
}
type dnsCache struct {
*Router
Req, Resp *dns.Msg
}
func (r *dnsCache) Fulfill(question string) (err error) {
conn := <-r.dns.connCh
var rtt time.Duration
r.Resp, rtt, err = r.dns.ExchangeWithConn(r.Req, conn)
if err != nil {
r.Resp, rtt, err = r.dns.ExchangeWithConn(r.Req, conn)
}
deferlog.Std.DebugWarn(err).
Dur("rtt", rtt).
Str("question", question).
Msg("exchange dns record")
select {
case r.dns.connCh <- conn:
default:
conn.Close()
}
return err
}
-130
View File
@@ -1,130 +0,0 @@
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, timeout time.Duration) error {
conn, err := net.DialTimeout("tcp", net.JoinHostPort(domain, p.String()), timeout)
if err != nil {
return err
}
defer conn.Close()
return p.PingWithConn(domain, conn, timeout)
}
// PingWithConn try connect to a http(s) server with domain though the http addr
func (p Port) PingWithConn(domain string, conn net.Conn, timeout time.Duration) error {
conn.SetDeadline(time.Now().Add(timeout))
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
_, err := conn.Read(make([]byte, 1))
if err == nil || err == io.EOF {
return 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()
}
+54
View File
@@ -0,0 +1,54 @@
package router
import (
"net/http"
"sync"
"time"
"github.com/rs/zerolog/log"
)
var pingClient = http.Client{
Timeout: 2 * time.Second,
}
func (r *Router) isAccess(domain string, port uint16) bool {
switch port {
case 80:
case 443:
default:
return false
}
p := &ping{}
_ = r.accessCache.Remember(p, domain)
return p.isAccess
}
type ping struct {
isAccess bool
}
func (p *ping) Fulfill(key string) error {
wg := sync.WaitGroup{}
wg.Add(2)
var err80, err443 error
go func() {
_, err80 = pingClient.Head("http://" + key)
wg.Done()
}()
go func() {
_, err443 = pingClient.Head("https://" + key)
wg.Done()
}()
wg.Wait()
if err80 != nil && err443 != nil {
log.Warn().
Errs("errs", []error{err80, err443}).
Msg("Failed to ping")
}
p.isAccess = (err80 == nil && err443 == nil)
return nil
}
+122 -119
View File
@@ -2,141 +2,144 @@ package router
import (
"net"
"strings"
"sync"
"sync/atomic"
"strconv"
"time"
"github.com/wweir/sower/transport"
"github.com/wweir/utils/log"
"github.com/wweir/utils/mem"
"github.com/miekg/dns"
geoip2 "github.com/oschwald/geoip2-golang"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
"github.com/wweir/deferlog"
"github.com/wweir/sower/pkg/dhcp"
"github.com/wweir/sower/pkg/mem"
"github.com/wweir/sower/util"
)
type Route struct {
port Port
once sync.Once
cache *mem.Cache
type ProxyDialFn func(network, host string, port uint16) (net.Conn, error)
type Router struct {
blockRule *util.Node
directRule *util.Node
proxyRule *util.Node
ProxyDial ProxyDialFn
accessCache *mem.Cache
ProxyAddress string
ProxyPassword string
password []byte
dns struct {
dns.Client
fallbackDNS string
serveIP net.IP
connCh chan *dns.Conn
cache *mem.Cache
}
DetectLevel int // dynamic detect proxy level
DetectTimeout string // dynamic detect timeout
timeout time.Duration
DirectList []string
directRule *Node
ProxyList []string
proxyRule *Node
DynamicList []string
dynamicRule *Node
PersistFn func(string)
country struct {
*geoip2.Reader
cidrs []*net.IPNet
}
}
// ShouldProxy check if the domain shoule request though proxy
func (r *Route) ShouldProxy(domain string) bool {
r.once.Do(func() {
r.cache = mem.New(4 * time.Hour)
r.password = []byte(r.ProxyPassword)
r.directRule = NewNodeFromRules(r.DirectList...)
r.proxyRule = NewNodeFromRules(r.ProxyList...)
r.dynamicRule = NewNodeFromRules(r.DynamicList...)
if timeout, err := time.ParseDuration(r.DetectTimeout); err != nil {
r.timeout = 200 * time.Millisecond
log.Warnw("parse detect timeout", "err", err, "default", "t.timeout")
} else {
r.timeout = timeout
}
})
// break deadlook, for wildcard
if strings.Count(domain, ".") > 4 {
return false
}
domain = strings.TrimSuffix(domain, ".")
if domain == r.ProxyAddress {
return false
}
if r.directRule.Match(domain) {
return false
}
if r.proxyRule.Match(domain) {
return true
func NewRouter(serveIP, fallbackDNS, mmdbFile string, proxyDial ProxyDialFn) *Router {
r := Router{
ProxyDial: proxyDial,
accessCache: mem.New(time.Hour), // TODO: config
}
r.cache.Remember(r, domain)
return r.dynamicRule.Match(domain)
r.dns.serveIP = net.ParseIP(serveIP)
r.dns.fallbackDNS = fallbackDNS
r.dns.connCh = make(chan *dns.Conn, 1)
r.dns.cache = mem.New(5 * time.Minute) // Tll: 10 minutes
go r.dialDNSConn()
var err error
r.country.Reader, err = geoip2.Open(mmdbFile)
log.Err(err).Str("file", mmdbFile).Msg("open geoip2 db")
return &r
}
func (r *Route) Get(key interface{}) (err error) {
domain := key.(string)
func (r *Router) SetRules(blockList, directList, proxyList, directCIDRs []string) {
if r.detect(domain) > r.DetectLevel {
r.dynamicRule.Add(domain)
if r.PersistFn != nil {
r.PersistFn(domain)
r.blockRule = util.NewNodeFromRules(blockList...)
r.directRule = util.NewNodeFromRules(directList...)
r.proxyRule = util.NewNodeFromRules(proxyList...)
r.country.cidrs = make([]*net.IPNet, 0, len(directCIDRs))
for _, cidr := range directCIDRs {
_, ipnet, err := net.ParseCIDR(cidr)
if err != nil {
log.Error().Err(err).Msg("Failed to parse CIDR")
}
r.country.cidrs = append(r.country.cidrs, ipnet)
}
}
func (r *Router) dialDNSConn() {
for {
server, err := dhcp.GetDNSServer()
log.Err(err).
Str("DNS", server).
Str("fallback", r.dns.fallbackDNS).
Msg("get DNS server")
if server == "" {
server = r.dns.fallbackDNS
}
for {
conn, err := dns.DialTimeout("udp", net.JoinHostPort(server, "53"), time.Second)
if err != nil {
log.Error().Err(err).Str("ip", server).Msg("dial dns server")
break
}
r.dns.connCh <- conn
}
}
}
func (r *Router) RouteHandle(conn net.Conn, domain string, port uint16) (err error) {
start := time.Now()
defer func() {
deferlog.DebugWarn(err).
Str("domain", domain).
Uint16("port", port).
Dur("spend", time.Since(start)).
Msg("serve socks5")
}()
addr := net.JoinHostPort(domain, strconv.FormatUint(uint64(port), 10))
// 1. rule_based( block > direct > proxy )
// 2. detect_based( CN IP || access site )
// 3. fallback( proxy )
switch {
case r.blockRule.Match(domain):
return nil
case r.directRule.Match(domain):
return r.DirectHandle(conn, addr)
case r.proxyRule.Match(domain):
return r.ProxyHandle(conn, domain, port)
case r.localSite(domain), r.isAccess(domain, port):
return r.DirectHandle(conn, addr)
default:
return r.ProxyHandle(conn, domain, port)
}
}
func (r *Router) ProxyHandle(conn net.Conn, domain string, port uint16) error {
start := time.Now()
rc, err := r.ProxyDial("tcp", domain, port)
if err != nil {
return errors.Wrapf(err, "proxy dial (%s:%d), spend (%s)", domain, port, time.Since(start))
}
defer rc.Close()
util.Relay(conn, rc)
return nil
}
// detect and caculate direct connection and proxy connection score
func (r *Route) detect(domain string) int {
wg := sync.WaitGroup{}
httpScore, httpsScore := new(int32), new(int32)
for _, ping := range [...]*Route{{port: HTTP}, {port: HTTPS}} {
wg.Add(1)
go func(ping *Route) {
defer wg.Done()
if err := ping.port.Ping(domain, r.timeout); err != nil {
return
}
switch ping.port {
case HTTP:
if !atomic.CompareAndSwapInt32(httpScore, 0, -2) {
atomic.AddInt32(httpScore, -1)
}
case HTTPS:
if !atomic.CompareAndSwapInt32(httpsScore, 0, -2) {
atomic.AddInt32(httpScore, -1)
}
}
}(ping)
}
for _, ping := range [...]*Route{{port: HTTP}, {port: HTTPS}} {
wg.Add(1)
go func(ping *Route) {
defer wg.Done()
target := net.JoinHostPort(domain, ping.port.String())
conn, err := transport.Dial(r.ProxyAddress, target, r.password)
if err != nil {
log.Errorw("sower dial", "addr", r.ProxyAddress, "err", err)
return
}
if err := ping.port.PingWithConn(domain, conn, r.timeout); err != nil {
return
}
switch ping.port {
case HTTP:
if !atomic.CompareAndSwapInt32(httpScore, 0, 2) {
atomic.AddInt32(httpScore, 1)
}
case HTTPS:
if !atomic.CompareAndSwapInt32(httpsScore, 0, 2) {
atomic.AddInt32(httpScore, 1)
}
}
}(ping)
}
wg.Wait()
return int(*httpScore + *httpsScore)
func (r *Router) DirectHandle(conn net.Conn, addr string) error {
dur, err := util.RelayTo(conn, addr)
return errors.Wrapf(err, "spend (%s)", dur)
}
+215
View File
@@ -0,0 +1,215 @@
<svg host="65bd71144e" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="351px" height="491px" viewBox="-0.5 -0.5 351 491" content="&lt;mxfile&gt;&lt;diagram id=&quot;u2y3ysnPqCVZKoHCRF4D&quot; name=&quot;第 1 页&quot;&gt;3VlNc9owEP01HJPxB/7gGCBtD2mbGdppcxRY2GpsyyOLAv31XSEZW5ZJKHEIzSWR1itZem/1dmUG7iTbfGSoSD7TCKcDx4o2A3c6cJyRH8JfYdhKQxAE0hAzEkmTXRtm5A9WRktZVyTCpebIKU05KXTjguY5XnDNhhija91tSVP9rQWKsWGYLVBqWn+QiCfSGjpBbf+ESZxUb7b9kXySocpZ7aRMUETXDZN7O3AnjFIuW9lmglOBXYWLHPfhwNP9whjO+TEDxDMx4jdKV2pzamF8W+2W0VUeYTHAGrjjdUI4nhVoIZ6ugV6wJTxLoWdDU02HGcebg2uy9zuFCME0w5xtwUUNcDwFjooOO1D9dY21W/kkDZyHI2VEit94P3cNATQUCt2IDA1APn37dj8zUMERhITqUsYTGtMcpbe1dazjVvvcUVootH5hzrcqvtGKUx1LwIttf4rx117VfWg+m27U5LK3Vb0DgMPy6Yot1PoVzxyxGCsvV5rEzp7kieEUcfJbPwwvwdw2QS/p4rH0wLYqSs4wyt6EgA3hEn/HU92HCnFo1/CLzino9we1GnpPCby1PkqhfpRcv3VCJP1qVE3YDWNo23ArhEN5+D2u1/2emn85Yx0N+z0eFyD+29If/J/0e/556N8L72vRHxj6EBEmMjvYSEl4Z3TcoTmUHRqjKCVxDu0FkIMZGESiIpDYb9SDjESRDB5ckj9ovptP0KoggMm98cCbdhPtH8x+qhJRE2rJvisFWteW7QcapldqplNDonKhy2WJeYuefyXErBpKugZAHT8FLMZz0Yr5DqS2ha1SKN1gPXkkKkDMJY1zVGJhgEMrmOmhBuk4ggYxh2uQlnAObbMG2fs0axDb6qEGscO3kLujlKv3GuGAojg6/k4b1gPKdYq4jN5XbjkXQ6OzMeR2q010cSrhDI9UibAHkQguViNUGmxeLV6n4PnXOsWzPI0vz23djp9RoZb/i+uasCOw5bXHR5kI13xein+wKBhIYrxbXMHoZnsZFU9Vl7244Lmyrl3bt/WKZ/iyiqdaUGtELwWQbzA3Fp+VdiWQRh04fQdzafBVJqgQzVWW3iw4bVKzo/GeQl1LqKBoTjmnWQd3nLY0DYqnlOR4sv/u9WTWOF7o2mK/v1g0P8l06Fz7vnFSMWR+kTprfj4+PYvOPWYEtiiO2vEpe3QuwXxe4ZxX+kDgXpby2mZNMf0yq+4fgotE/M2FUFjLlK7Nc30J+ludjV4EeBiEoQb6Jd04RwZfX4Ei9j7k1W1H+xnltXp1A9klehTBDwBhA1fYEX+9CBc6XEpJFlOXnNFHgDsV/E1zmuOe0lnru5gdmnj7HXi3xfEkvG0Db6X77xlxN/SeRTzoB3Ho1r+fSaWpf4R0b/8C&lt;/diagram&gt;&lt;/mxfile&gt;">
<defs/>
<g>
<rect x="0" y="0" width="350" height="490" fill="#ffffff" stroke="#000000" pointer-events="all"/>
<path d="M 170 240 L 170 156.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 170 151.12 L 173.5 158.12 L 170 156.37 L 166.5 158.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 195px; margin-left: 170px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">
HTTPS
</div>
</div>
</div>
</foreignObject>
<text x="170" y="198" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle">
HTTPS
</text>
</switch>
</g>
<path d="M 100 240 L 100 190 L 36.37 190" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 31.12 190 L 38.12 186.5 L 36.37 190 L 38.12 193.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 190px; margin-left: 90px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">
socks5 upstream
</div>
</div>
</div>
</foreignObject>
<text x="90" y="193" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle">
socks5 upstream
</text>
</switch>
</g>
<path d="M 240 240 L 240 190 L 303.63 190" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 308.88 190 L 301.88 193.5 L 303.63 190 L 301.88 186.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 192px; margin-left: 252px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">
direct visit
</div>
</div>
</div>
</foreignObject>
<text x="252" y="195" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle">
direct visit
</text>
</switch>
</g>
<rect x="30" y="240" width="280" height="100" fill="#ffffff" stroke="#000000" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 278px; height: 1px; padding-top: 290px; margin-left: 31px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
sower
<br/>
<br/>
rules and detect based router
</div>
</div>
</div>
</foreignObject>
<text x="170" y="294" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
sower...
</text>
</switch>
</g>
<path d="M 170 70 L 170 36.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 170 31.12 L 173.5 38.12 L 170 36.37 L 166.5 38.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 240 70 L 240 36.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 240 31.12 L 243.5 38.12 L 240 36.37 L 236.5 38.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
<rect x="30" y="70" width="280" height="80" fill="#ffffff" stroke="#000000" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 278px; height: 1px; padding-top: 110px; margin-left: 31px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
sowerd
</div>
</div>
</div>
</foreignObject>
<text x="170" y="114" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
sowerd
</text>
</switch>
</g>
<path d="M 255 390 L 255 360 L 170 360 L 170 346.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 170 341.12 L 173.5 348.12 L 170 346.37 L 166.5 348.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 357px; margin-left: 251px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">
socks5 intelligent proxy
</div>
</div>
</div>
</foreignObject>
<text x="251" y="360" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle">
socks5 intelligent proxy
</text>
</switch>
</g>
<ellipse cx="255" cy="397.5" rx="7.5" ry="7.5" fill="#ffffff" stroke="#000000" pointer-events="all"/>
<path d="M 255 405 L 255 430 M 255 410 L 240 410 M 255 410 L 270 410 M 255 430 L 240 450 M 255 430 L 270 450" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 457px; margin-left: 255px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; ">
Browser  Users
</div>
</div>
</div>
</foreignObject>
<text x="255" y="469" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
Brows...
</text>
</switch>
</g>
<path d="M 85 390 L 85 360 L 170 360 L 170 346.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 170 341.12 L 173.5 348.12 L 170 346.37 L 166.5 348.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 360px; margin-left: 91px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">
DNS route the net flow
</div>
</div>
</div>
</foreignObject>
<text x="91" y="363" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle">
DNS route the net flow
</text>
</switch>
</g>
<ellipse cx="85" cy="397.5" rx="7.5" ry="7.5" fill="#ffffff" stroke="#000000" pointer-events="all"/>
<path d="M 85 405 L 85 430 M 85 410 L 70 410 M 85 410 L 100 410 M 85 430 L 70 450 M 85 430 L 100 450" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 457px; margin-left: 85px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; ">
Other Users
</div>
</div>
</div>
</foreignObject>
<text x="85" y="469" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
Other...
</text>
</switch>
</g>
<rect x="210" y="10" width="60" height="20" fill="none" stroke="none" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 20px; margin-left: 240px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; ">
fake site
</div>
</div>
</div>
</foreignObject>
<text x="240" y="24" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
fake site
</text>
</switch>
</g>
<rect x="135" y="10" width="70" height="20" fill="none" stroke="none" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 20px; margin-left: 170px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; ">
target site
</div>
</div>
</div>
</foreignObject>
<text x="170" y="24" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
target site
</text>
</switch>
</g>
</g>
<switch>
<g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/>
<a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank">
<text text-anchor="middle" font-size="10px" x="50%" y="100%">
Viewer does not support full SVG 1.1
</text>
</a>
</switch>
</svg>

After

Width:  |  Height:  |  Size: 18 KiB

-73
View File
@@ -1,73 +0,0 @@
package transport
import (
"crypto/md5"
"crypto/tls"
"encoding/binary"
"io"
"net"
"strconv"
"github.com/wweir/sower/util"
)
// checksum(>=0x80) + port + target_length + target + data
// data(HTTP/HTTPS, first byte < 0x7F)
type head struct {
Checksum byte
Port uint16
AddrLen uint8
}
func ToProxyConn(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 DialTlsProxyConn(address, tgtHost string, tgtPort uint16, tlsCfg *tls.Config, password []byte) (net.Conn, error) {
conn, err := tls.Dial("tcp", address, tlsCfg)
if err != nil {
return nil, err
}
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
}
data := []byte(tgtHost)
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
}
-133
View File
@@ -1,133 +0,0 @@
package transport
import (
"encoding/binary"
"fmt"
"io"
"net"
"strconv"
"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, address string) (net.Conn, error) {
host, port, err := net.SplitHostPort(address)
if err != nil {
return nil, err
}
p, err := strconv.Atoi(port)
if err != nil {
return nil, err
}
return &conn{
init: make(chan struct{}),
Conn: c,
domain: host,
port: []byte{byte(p >> 8), byte(p)},
}, nil
}
type conn struct {
init chan struct{}
domain string
port []byte
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
}
}
{
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: c.port,
}
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)
}
+125
View File
@@ -0,0 +1,125 @@
package socks5
import (
"encoding/binary"
"io"
"net"
)
// https://tools.ietf.org/html/rfc1928
// 1. client send auth request
type authReq struct {
VER byte
NMETHODS uint8
METHODS []byte
}
func (req *authReq) Fulfill(r io.Reader) error {
buf := make([]byte, 2)
if n, err := r.Read(buf); err != nil || n != 2 {
return err
}
req.VER = buf[0]
req.NMETHODS = buf[1]
req.METHODS = make([]byte, int(req.NMETHODS))
if n, err := r.Read(req.METHODS); err != nil || n != len(req.METHODS) {
return err
}
return nil
}
func (r *authReq) IsValid() bool {
return r.VER == 5 && r.METHODS[0] == 0
}
// 2. server response auth request
type authResp struct {
VER byte
METHOD byte
}
// 3. client request with target address
type reqHead struct {
VER byte
CMD byte
RSV byte
ATYP byte
}
func (r *reqHead) IsValid() bool {
return r.VER == 5 && r.CMD == 1
}
// 4. server response with the address that assigned to connect to target address
type respHead struct {
VER byte
REP byte
RSV byte
ATYP byte
BND_ADDR [net.IPv4len]byte
BND_PORT uint16
}
type addrType interface {
Fulfill(r io.Reader) error
Addr() (domain string, port uint16)
}
// ATYP:
// 0x01 -> net.IPv4len
// 0x03 -> first byte is length
// 0x04 -> net.IPv6len
type addrTypeIPv4 struct {
DST_ADDR [4]byte
DST_PORT uint16
}
func (a *addrTypeIPv4) Fulfill(r io.Reader) error {
return binary.Read(r, binary.BigEndian, a)
}
func (a *addrTypeIPv4) Addr() (string, uint16) {
return net.IP(a.DST_ADDR[:]).String(), a.DST_PORT
}
type addrTypeDomain struct {
DST_ADDR_LEN uint8
DST_ADDR []byte
DST_PORT uint16
}
func (a *addrTypeDomain) Fulfill(r io.Reader) error {
buf := make([]byte, 1)
if _, err := io.ReadFull(r, buf); err != nil {
return err
}
a.DST_ADDR_LEN = uint8(buf[0])
buf = make([]byte, a.DST_ADDR_LEN+2)
if _, err := io.ReadFull(r, buf); err != nil {
return err
}
a.DST_ADDR = buf[:int(a.DST_ADDR_LEN)]
a.DST_PORT = binary.BigEndian.Uint16(buf[int(a.DST_ADDR_LEN):])
return nil
}
func (a *addrTypeDomain) Addr() (string, uint16) {
return string(a.DST_ADDR[:]), a.DST_PORT
}
type addrTypeIPv6 struct {
DST_ADDR [16]byte
DST_PORT uint16
}
func (a *addrTypeIPv6) Fulfill(r io.Reader) error {
return binary.Read(r, binary.BigEndian, a)
}
func (a *addrTypeIPv6) Addr() (string, uint16) {
return net.IP(a.DST_ADDR[:]).String(), a.DST_PORT
}
+113
View File
@@ -0,0 +1,113 @@
package socks5
import (
"bytes"
"encoding/binary"
"net"
"strconv"
"github.com/pkg/errors"
)
type AddrHead struct {
addrType
}
func (h *AddrHead) Network() string { return "tcp" }
func (h *AddrHead) String() string {
host, port := h.Addr()
return net.JoinHostPort(host, strconv.Itoa(int(port)))
}
// Socks5 is a SOCKS5 proxy. It implements the teeconn.Conn interface.
// It is used to be a second relay of other proxy tools.
// user -> sower -socks5-> third-party proxy -> target
type Socks5 struct{}
func New() *Socks5 {
return &Socks5{}
}
var noAuthResp = authResp{VER: 5, METHOD: 0}
var succHeadResp = respHead{VER: 5, REP: 0, RSV: 0, ATYP: 1}
func (s *Socks5) Unwrap(conn net.Conn) (net.Addr, error) {
{ //auth
auth := new(authReq)
if err := auth.Fulfill(conn); err != nil && !auth.IsValid() {
return nil, errors.Errorf("read head: %v, err: %s", auth, err)
}
if err := binary.Write(conn, binary.BigEndian, noAuthResp); err != nil {
return nil, errors.Wrap(err, "write auth")
}
}
var addr addrType
{ // head
head := new(reqHead)
if err := binary.Read(conn, binary.BigEndian, head); err != nil || !head.IsValid() {
return nil, errors.Errorf("read head: %v, err: %s", head, err)
}
switch head.ATYP {
case 0x01: // IPv4
addr = &addrTypeIPv4{}
case 0x03: // domain name
addr = &addrTypeDomain{}
case 0x04: // IPv6
addr = &addrTypeIPv6{}
default:
return nil, errors.New("invalid ATYP")
}
if err := addr.Fulfill(conn); err != nil {
return nil, errors.Wrap(err, "read target")
}
if err := binary.Write(conn, binary.BigEndian, succHeadResp); err != nil {
return nil, errors.Wrap(err, "write head")
}
}
return &AddrHead{
addrType: addr,
}, nil
}
var noAuthReq = struct {
VER byte
NMETHODS uint8
METHODS byte
}{5, 1, 0}
var domainHead = reqHead{VER: 5, CMD: 1, RSV: 0, ATYP: 3}
func (s *Socks5) Wrap(conn net.Conn, tgtHost string, tgtPort uint16) error {
{ // auth
if err := binary.Write(conn, binary.BigEndian, &noAuthReq); err != nil {
return errors.WithStack(err)
}
resp := &authResp{}
if err := binary.Read(conn, binary.BigEndian, resp); err != nil {
return errors.WithStack(err)
}
}
{ // head
buf := bytes.NewBuffer(make([]byte, 0, binary.Size(domainHead)+1+len(tgtHost)+2))
_ = binary.Write(buf, binary.BigEndian, domainHead)
buf.WriteByte(uint8(len(tgtHost)))
buf.WriteString(tgtHost)
buf.Write([]byte{byte(tgtPort >> 8), byte(tgtPort)})
if _, err := conn.Write(buf.Bytes()); err != nil {
return errors.WithStack(err)
}
head := respHead{}
if err := binary.Read(conn, binary.BigEndian, &head); err != nil {
return errors.WithStack(err)
}
}
return nil
}
-44
View File
@@ -1,44 +0,0 @@
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
}
+81
View File
@@ -0,0 +1,81 @@
package sower
import (
"bytes"
"crypto/md5"
"encoding/binary"
"net"
"strconv"
"github.com/pkg/errors"
)
// https://en.wikipedia.org/wiki/Domain_Name_System
const maxDomainLength = 253
var headSize = binary.Size(new(Head))
// action(>=0x80) + checksum + port + target + data
// data(HTTP, first byte < 0x7F)
type Head struct {
Cmd byte
Checksum uint64
Port uint16
TgtAddr [maxDomainLength]byte
}
func (h *Head) Network() string { return "tcp" }
func (h *Head) String() string {
idx := bytes.IndexRune(h.TgtAddr[:], 0)
addr := string(h.TgtAddr[:idx])
return net.JoinHostPort(addr, strconv.Itoa(int(h.Port)))
}
type Sower struct {
password []byte
}
func New(password string) *Sower {
return &Sower{
password: []byte(password),
}
}
func (s *Sower) Unwrap(conn net.Conn) (net.Addr, error) {
buf := make([]byte, headSize)
if n, err := conn.Read(buf); err != nil || n != headSize {
return nil, errors.Errorf("n: %d, err: %s", n, err)
}
h := &Head{}
_ = binary.Read(bytes.NewReader(buf), binary.BigEndian, h)
switch h.Cmd {
case 0x80:
default:
return nil, errors.Errorf("invalid command: %d", h.Cmd)
}
if h.Checksum != sumChecksum(h.TgtAddr, s.password) {
return nil, errors.New("auth fail")
}
return h, nil
}
func (s *Sower) Wrap(conn net.Conn, tgtHost string, tgtPort uint16) error {
tgtAddr := [maxDomainLength]byte{}
copy(tgtAddr[:len(tgtHost)], []byte(tgtHost))
return binary.Write(conn, binary.BigEndian, &Head{
Cmd: 0x80,
Checksum: sumChecksum(tgtAddr, s.password),
Port: tgtPort,
TgtAddr: tgtAddr,
})
}
func sumChecksum(target [maxDomainLength]byte, password []byte) uint64 {
checksum := md5.Sum(append(target[:], password...))
checksumVal, _ := binary.Uvarint(checksum[:])
return checksumVal
}
+10
View File
@@ -0,0 +1,10 @@
package transport
import (
"net"
)
type Transport interface {
Unwrap(conn net.Conn) (net.Addr, error)
Wrap(conn net.Conn, tgtHost string, tgtPort uint16) error
}
+46
View File
@@ -0,0 +1,46 @@
package transport
import (
"net"
"strings"
"testing"
"github.com/rs/zerolog/log"
"github.com/wweir/sower/pkg/teeconn"
"github.com/wweir/sower/transport/sower"
"github.com/wweir/sower/transport/trojan"
)
func init() {
log.Logger = log.Logger.With().Caller().Logger()
}
func testPipe(tran Transport) (net.Addr, error) {
r, w := net.Pipe()
defer r.Close()
go func(w net.Conn) {
defer w.Close()
tran.Wrap(w, "sower", 443)
}(w)
return tran.Unwrap(teeconn.New(r))
}
func Test_Transports(t *testing.T) {
if addr, err := testPipe(newSower()); err != nil || strings.TrimSpace(addr.String()) != "sower:443" {
t.Errorf("test sower, unexpected address: %s, err: %s", addr, err)
}
if addr, err := testPipe(newTrojan()); err != nil || strings.TrimSpace(addr.String()) != "sower:443" {
t.Errorf("test trojan, unexpected address: %s, err: %s", addr, err)
}
}
func newSower() *sower.Sower {
return sower.New("123")
}
func newTrojan() *trojan.Trojan {
return trojan.New("123")
}
+174
View File
@@ -0,0 +1,174 @@
package trojan
import (
"bytes"
"crypto/sha256"
"encoding/binary"
"encoding/hex"
"io"
"net"
"strconv"
"github.com/pkg/errors"
)
// +-----------------------+---------+----------------+---------+----------+
// | hex(SHA224(password)) | CRLF | Trojan Request | CRLF | Payload |
// +-----------------------+---------+----------------+---------+----------+
// | 56 | X'0D0A' | Variable | X'0D0A' | Variable |
// +-----------------------+---------+----------------+---------+----------+
// +-----+------+----------+----------+
// | CMD | ATYP | DST.ADDR | DST.PORT |
// +-----+------+----------+----------+
// | 1 | 1 | Variable | 2 |
// +-----+------+----------+----------+
// o CMD
// o CONNECT X'01'
// o UDP :X'03'
// o ATYP
// o IP V4 : X'01'
// o domain: X'03'
// o IP V6 : X'04'
const headLen = 56 + 2 + 1 + 1
type staticHead struct {
Passwd [56]byte
CRLF [2]byte
CMD uint8
ATYP uint8
}
type ipv4Addr struct {
ADDR [4]byte
PORT uint16
CRLF [2]byte
}
func (*ipv4Addr) Network() string { return "tcp" }
func (a *ipv4Addr) String() string {
return net.JoinHostPort(net.IP(a.ADDR[:]).String(), strconv.Itoa(int(a.PORT)))
}
type ipv6Addr struct {
ADDR [16]byte
PORT uint16
CRLF [2]byte
}
func (*ipv6Addr) Network() string { return "tcp" }
func (a *ipv6Addr) String() string {
return net.JoinHostPort(net.IP(a.ADDR[:]).String(), strconv.Itoa(int(a.PORT)))
}
type domain struct {
ADDR string
PORT uint16
CRLF [2]byte
}
func (*domain) Network() string { return "tcp" }
func (a *domain) String() string {
return net.JoinHostPort(a.ADDR, strconv.Itoa(int(a.PORT)))
}
func (a *domain) Fulfill(r io.Reader) error {
buf := make([]byte, 1)
if n, err := r.Read(buf); err != nil || n != 1 {
return errors.New("read domain length failed")
}
addrLen := int(buf[0])
buf = make([]byte, addrLen+4)
if n, err := r.Read(buf); err != nil || n != addrLen+4 {
return errors.Wrap(err, "read doamin failed")
}
a.ADDR = string(buf[:addrLen])
a.PORT = uint16(buf[addrLen])<<8 + uint16(buf[addrLen+1])
return nil
}
type Trojan struct {
headPasswd []byte
headIPv4 []byte
headIPv6 []byte
headDomain []byte
}
func New(password string) *Trojan {
t := &Trojan{
headPasswd: make([]byte, 56),
}
passSum := sha256.Sum224([]byte(password))
hex.Encode(t.headPasswd, passSum[:])
t.headIPv4 = append(t.headPasswd, 0x0D, 0x0A, 0x01, 0x01)
t.headIPv6 = append(t.headPasswd, 0x0D, 0x0A, 0x01, 0x04)
t.headDomain = append(t.headPasswd, 0x0D, 0x0A, 0x01, 0x03)
return t
}
func (t *Trojan) Unwrap(conn net.Conn) (net.Addr, error) {
buf := make([]byte, headLen)
// do not use io.ReadFull to avoid hang
if n, err := conn.Read(buf); err != nil || n != headLen {
return nil, errors.Errorf("n: %d, err: %s", n, err)
}
head := &staticHead{}
if err := binary.Read(bytes.NewBuffer(buf), binary.BigEndian, head); err != nil {
return nil, errors.Wrap(err, "read head")
}
if !bytes.Equal(head.Passwd[:], []byte(t.headPasswd)) {
return nil, errors.New("auth fail")
}
head.CMD, head.ATYP = buf[58], buf[59]
switch head.ATYP {
case 0x01: //ipv4
addr := &ipv4Addr{}
err := binary.Read(conn, binary.BigEndian, addr)
return addr, errors.Wrap(err, "read addr")
case 0x04: //ipv6
addr := &ipv6Addr{}
err := binary.Read(conn, binary.BigEndian, addr)
return addr, errors.Wrap(err, "read addr")
case 0x03: // domain
addr := &domain{}
err := addr.Fulfill(conn)
return addr, errors.Wrap(err, "read addr")
default:
return nil, errors.New("invalid ATYP")
}
}
func (t *Trojan) Wrap(conn net.Conn, tgtHost string, tgtPort uint16) error {
buf := bytes.NewBuffer(make([]byte, 0, headLen+1+len(tgtHost)+4))
ip := net.ParseIP(tgtHost)
switch {
case len(ip.To4()) != 0:
buf.Write(t.headIPv4)
buf.Write([]byte(ip.To4()))
case len(ip) != 0:
buf.Write(t.headIPv6)
buf.Write([]byte(ip.To16()))
default:
buf.Write(t.headDomain)
buf.WriteByte(byte(len(tgtHost)))
buf.WriteString(tgtHost)
}
buf.Write([]byte{byte(tgtPort >> 8), byte(tgtPort), 0x0D, 0x0A})
if n, err := conn.Write(buf.Bytes()); err != nil || n != len(buf.Bytes()) {
return errors.Errorf("n: %d, err: %s", n, err)
}
return nil
}
-36
View File
@@ -1,36 +0,0 @@
package transport
import (
"crypto/tls"
"net"
"strconv"
)
func Dial(address, target string, password []byte) (net.Conn, error) {
if addr, ok := IsSocks5Schema(address); ok {
conn, err := net.Dial("tcp", addr)
if err != nil {
return nil, err
}
if conn, err = ToSocks5(conn, target); err != nil {
conn.Close()
return nil, err
}
return conn, nil
}
host, port, err := net.SplitHostPort(target)
if err != nil {
port = "443"
}
p, err := strconv.Atoi(port)
if err != nil {
return nil, err
}
return DialTlsProxyConn(net.JoinHostPort(address, "443"), host, uint16(p), &tls.Config{
ServerName: address,
MinVersion: tls.VersionTLS12,
NextProtos: []string{"http/1.1", "h2"},
}, password)
}
+50
View File
@@ -0,0 +1,50 @@
package util
import (
"io"
"net"
"sync"
"sync/atomic"
"time"
"github.com/pkg/errors"
)
func RelayTo(conn net.Conn, addr string) (dur time.Duration, err error) {
if _, _, err := net.SplitHostPort(addr); err != nil {
addr = net.JoinHostPort(addr, "80")
}
start := time.Now()
rc, err := net.DialTimeout("tcp", addr, 5*time.Second)
if err != nil {
return time.Since(start), errors.WithStack(err)
}
defer rc.Close()
Relay(conn, rc)
return time.Since(start), nil
}
func Relay(conn1, conn2 net.Conn) {
wg := &sync.WaitGroup{}
exitFlag := new(int32)
wg.Add(2)
go redirect(conn2, conn1, wg, exitFlag)
redirect(conn1, conn2, wg, exitFlag)
wg.Wait()
}
func redirect(dst, src net.Conn, wg *sync.WaitGroup, exitFlag *int32) {
// io.Copy(dst, io.TeeReader(src, os.Stdout))
io.Copy(dst, src)
if atomic.CompareAndSwapInt32(exitFlag, 0, 1) {
// wakeup blocked goroutine
now := time.Now()
src.SetDeadline(now)
dst.SetDeadline(now)
}
wg.Done()
}
@@ -1,4 +1,4 @@
package router
package util
import (
"strings"
@@ -49,7 +49,7 @@ func (n *node) add(secs []string) {
case 0:
return
case 1:
n.node[secs[length-1]] = &node{node: map[string]*node{"": &node{}}}
n.node[secs[length-1]] = &node{node: map[string]*node{"": {}}}
default:
sec := secs[length-1]
if sec == "**" {
@@ -1,9 +1,9 @@
package router_test
package util_test
import (
"testing"
"github.com/wweir/sower/router"
"github.com/wweir/sower/util"
)
func TestNode_Match(t *testing.T) {
@@ -13,18 +13,18 @@ func TestNode_Match(t *testing.T) {
}
tests := []struct {
name string
node *router.Node
node *util.Node
tests []test
}{{
"simple",
router.NewNodeFromRules("a.wweir.cc", "b.wweir.cc"),
util.NewNodeFromRules("a.wweir.cc", "b.wweir.cc"),
[]test{
{"a.wweir.cc", true},
{"b.wweir.cc", true},
},
}, {
"parent",
router.NewNodeFromRules("wweir.cc", "a.wweir.cc"),
util.NewNodeFromRules("wweir.cc", "a.wweir.cc"),
[]test{
{"wweir.cc", true},
{"a.wweir.cc", true},
@@ -32,7 +32,7 @@ func TestNode_Match(t *testing.T) {
},
}, {
"fuzz1",
router.NewNodeFromRules("wweir.cc", "a.wweir.cc", "*.wweir.cc"),
util.NewNodeFromRules("wweir.cc", "a.wweir.cc", "*.wweir.cc"),
[]test{
{"wweir.cc", true},
{"a.wweir.cc", true},
@@ -41,7 +41,7 @@ func TestNode_Match(t *testing.T) {
},
}, {
"fuzz2",
router.NewNodeFromRules("a.*.cc", "c.wweir.*"),
util.NewNodeFromRules("a.*.cc", "c.wweir.*"),
[]test{
{"wweir.cc", false},
{"a.wweir.cc", true},
@@ -50,7 +50,7 @@ func TestNode_Match(t *testing.T) {
},
}, {
"fuzz3",
router.NewNodeFromRules("*.*.cc", "iamp.*.*"),
util.NewNodeFromRules("*.*.cc", "iamp.*.*"),
[]test{
{"wweir.cc", false},
{"a.wweir.cc", true},
@@ -59,7 +59,7 @@ func TestNode_Match(t *testing.T) {
},
}, {
"fuzz4",
router.NewNodeFromRules("**.cc", "a.**.com", "**.wweir.*"),
util.NewNodeFromRules("**.cc", "a.**.com", "**.wweir.*"),
[]test{
{"wweir.cc", true},
{"a.wweir.cc", true},
-50
View File
@@ -1,50 +0,0 @@
package util
import (
"io"
"net"
)
type TeeConn struct {
net.Conn
buf []byte
offset int
stop bool // read
EnableWrite bool
}
func (t *TeeConn) Reread() {
t.offset = 0
}
func (t *TeeConn) Reset() {
t.buf = []byte{}
t.offset = 0
}
func (t *TeeConn) Stop() {
t.offset = 0
t.stop = true
}
func (t *TeeConn) Read(b []byte) (n int, err error) {
length := len(t.buf) - t.offset
if length > 0 {
n = copy(b, t.buf[t.offset:])
t.offset += n
return
}
n, err = t.Conn.Read(b)
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
}