mirror of
https://github.com/wweir/sower.git
synced 2026-10-19 16:14:33 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dab1a78bbf | ||
|
|
13a6be47a4 | ||
|
|
56b8e35fe7 | ||
|
|
47b8fc601f | ||
|
|
2dc128d1c8 | ||
|
|
2a4776dc28 | ||
|
|
d121427bbb | ||
|
|
91582d6095 | ||
|
|
396b08c0ac | ||
|
|
78b6d4df2e | ||
|
|
1574623507 | ||
|
|
ee6335fa47 | ||
|
|
f7214bfdb4 | ||
|
|
291ae3f85a | ||
|
|
07c51d42ff | ||
|
|
90c497b8da | ||
|
|
28bd12d220 | ||
|
|
c0f39fc2e0 | ||
|
|
bcc6d7c504 | ||
|
|
5d1827b2da | ||
|
|
c38d519f92 | ||
|
|
dd4444b71c | ||
|
|
899d64a92c | ||
|
|
7d77f4a85e | ||
|
|
4681dd9845 | ||
|
|
85226890c5 | ||
|
|
1a24286bbc | ||
|
|
fd5c34dca1 | ||
|
|
8cd4fe57ba | ||
|
|
545b153f50 | ||
|
|
697e2aa1d5 | ||
|
|
2fe1708d3f | ||
|
|
cbe943a020 | ||
|
|
d7e25d6565 | ||
|
|
acb3f58526 | ||
|
|
50684272c9 | ||
|
|
b0428177de | ||
|
|
75e7f1aab7 | ||
|
|
f256ab686d | ||
|
|
0c0f0699c1 | ||
|
|
8532a2e16c | ||
|
|
bad338033c | ||
|
|
a57f85ab1b | ||
|
|
aed2afbe6d | ||
|
|
2becd42176 | ||
|
|
d3664dc2b9 | ||
|
|
a6fd60b5a2 | ||
|
|
d6dbfd4129 | ||
|
|
d31bbf9226 | ||
|
|
ac4e4266bc | ||
|
|
f9f0ca4ee7 | ||
|
|
98c3934762 | ||
|
|
f001f7dab7 | ||
|
|
36ef329b68 | ||
|
|
64778f68ac | ||
|
|
53748cfa85 | ||
|
|
d35115b7b3 | ||
|
|
559c5fde6b | ||
|
|
eb8080df18 | ||
|
|
45ca063a7e | ||
|
|
6add7afd19 | ||
|
|
f13687506e | ||
|
|
199e4cf295 | ||
|
|
b3ad9f6657 | ||
|
|
f9e9fe366f | ||
|
|
103699e25e | ||
|
|
f80755eae5 | ||
|
|
5b217b794d | ||
|
|
26055c5203 | ||
|
|
029611c078 | ||
|
|
89adc6a2c9 | ||
|
|
8c95c59e5e | ||
|
|
4f8376c041 | ||
|
|
10172f8be2 | ||
|
|
2831cdf284 | ||
|
|
06558ac635 | ||
|
|
3cf4deddb7 | ||
|
|
679263be9a | ||
|
|
21d8ec4ec7 | ||
|
|
4916245522 | ||
|
|
11c2808320 | ||
|
|
21ddd79e4d | ||
|
|
9a158d4088 | ||
|
|
88990cddd9 | ||
|
|
ca1b2650ab | ||
|
|
8ff1046ebf | ||
|
|
dfe81f6f23 | ||
|
|
972875ec36 | ||
|
|
89b7a41d46 | ||
|
|
8f598dbc17 | ||
|
|
192b4f798b | ||
|
|
2adefe0dfc | ||
|
|
7b0c13ef1c | ||
|
|
63bb934148 | ||
|
|
cacc732a4e |
@@ -1,56 +0,0 @@
|
||||
version: 2
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
- image: circleci/golang:1.11.4
|
||||
steps:
|
||||
- checkout # check out the code in the project directory
|
||||
- setup_remote_docker # install docker in docker
|
||||
- run:
|
||||
name: Prepare Environment
|
||||
command: |
|
||||
cp conf/sower.toml .
|
||||
cp deploy/* .
|
||||
mkdir artifacts
|
||||
- run:
|
||||
name: Run Unit Test
|
||||
command: |
|
||||
make test
|
||||
- run:
|
||||
name: Build linux Binary
|
||||
command: |
|
||||
make build
|
||||
tar czvf artifacts/sower_linux_amd64.tar.gz sower sower.toml sower-client.service sower-server.service
|
||||
- run:
|
||||
name: Build macOS Binary
|
||||
command: |
|
||||
GOOS=darwin GOARCH=amd64 make build
|
||||
tar czvf artifacts/sower_darwin_amd64.tar.gz sower sower.toml cc.wweir.sower.plist
|
||||
- run:
|
||||
name: Build Windows Binary
|
||||
command: |
|
||||
GOOS=windows GOARCH=amd64 make build
|
||||
tar czvf artifacts/sower_windows_amd64.tar.gz sower sower.toml
|
||||
- run:
|
||||
name: Publish Release on GitHub
|
||||
command: |
|
||||
git describe --tags | grep - && exit
|
||||
|
||||
GHR_VERSION="$(curl -s "https://api.github.com/repos/tcnksm/ghr/releases/latest" | awk -F '"' '/tag_name/{printf $4}')"
|
||||
curl -SLf https://github.com/tcnksm/ghr/releases/download/"$GHR_VERSION"/ghr_"$GHR_VERSION"_linux_amd64.tar.gz | tar xzC /go/bin --strip-components=1
|
||||
|
||||
VERSION=$(git describe --tags)
|
||||
ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -delete ${VERSION%-*-*} artifacts/
|
||||
- run:
|
||||
name: Build Docker Image
|
||||
command: |
|
||||
git describe --tags | grep - && exit
|
||||
|
||||
docker login -u $DOCKER_USER -p $DOCKER_PASS
|
||||
make image
|
||||
VERSION=$(git describe --tags)
|
||||
docker tag sower wweir/sower:${VERSION%-*-*}
|
||||
docker push wweir/sower:${VERSION%-*-*}
|
||||
docker tag sower wweir/sower:latest
|
||||
docker push wweir/sower:latest
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# Compile
|
||||
FROM golang:1.14-alpine AS compiler
|
||||
|
||||
RUN apk add --no-cache git make
|
||||
|
||||
# enable go modules
|
||||
WORKDIR /src
|
||||
COPY . .
|
||||
|
||||
RUN CGO_ENABLED=0 make build
|
||||
|
||||
|
||||
# Build image
|
||||
FROM scratch
|
||||
|
||||
COPY --from=compiler /src/sower /sower
|
||||
ENTRYPOINT [ "/sower" ]
|
||||
@@ -0,0 +1,16 @@
|
||||
name: Go
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-go@v2-beta
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: test and build
|
||||
run: |
|
||||
make
|
||||
@@ -0,0 +1,53 @@
|
||||
name: Release
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*.*.*"
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-go@v2-beta
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: build matrix
|
||||
run: |
|
||||
cp conf/sower.toml sower.toml
|
||||
|
||||
GOOS=linux GOARCH=amd64 make build
|
||||
tar czvf sower-linux-amd64.tar.gz sower sower.toml
|
||||
GOOS=linux GOARCH=386 make build
|
||||
tar czvf sower-linux-386.tar.gz sower sower.toml
|
||||
GOOS=linux GOARCH=arm make build
|
||||
tar czvf sower-linux-arm.tar.gz sower sower.toml
|
||||
GOOS=linux GOARCH=arm64 make build
|
||||
tar czvf sower-linux-arm64.tar.gz sower sower.toml
|
||||
GOOS=linux GOARCH=mips make build
|
||||
tar czvf sower-linux-mips.tar.gz sower sower.toml
|
||||
GOOS=linux GOARCH=mipsle make build
|
||||
tar czvf sower-linux-mipsle.tar.gz sower sower.toml
|
||||
|
||||
GOOS=darwin GOARCH=amd64 make build
|
||||
tar czvf sower-darwin-amd64.tar.gz sower sower.toml
|
||||
|
||||
GOOS=windows GOARCH=amd64 make build
|
||||
tar czvf sower-windows-amd64.tar.gz sower.exe sower.toml
|
||||
GOOS=windows GOARCH=386 make build
|
||||
tar czvf sower-windows-386.tar.gz sower.exe sower.toml
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@master
|
||||
with:
|
||||
files: |
|
||||
sower-linux-amd64.tar.gz
|
||||
sower-linux-386.tar.gz
|
||||
sower-linux-arm.tar.gz
|
||||
sower-linux-arm64.tar.gz
|
||||
sower-linux-mips.tar.gz
|
||||
sower-linux-mipsle.tar.gz
|
||||
sower-darwin-amd64.tar.gz
|
||||
sower-windows-amd64.tar.gz
|
||||
sower-windows-386.tar.gz
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -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.
|
||||
|
||||
@@ -1,31 +1,12 @@
|
||||
SERVER:=127.0.0.1:5533
|
||||
|
||||
default: test build
|
||||
|
||||
generate:
|
||||
go generate ./...
|
||||
test:
|
||||
go test -race ./...
|
||||
go vet ./...
|
||||
go list ./... | grep -v internal | xargs go test
|
||||
build:
|
||||
GO111MODULE=on go build -v -ldflags \
|
||||
"-X main.version=$(shell git describe --tags) \
|
||||
-X main.date=$(shell date +%Y-%m-%d)"
|
||||
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 deploy/Dockerfile .
|
||||
docker build -t sower -f .github/Dockerfile .
|
||||
|
||||
kill:
|
||||
sudo pkill -9 sower || true
|
||||
|
||||
client: build kill
|
||||
sudo $(PWD)/sower
|
||||
|
||||
server: build kill
|
||||
$(PWD)/sower -n TCP -v 1
|
||||
|
||||
run: build kill
|
||||
$(PWD)/sower -n TCP -v 1 &
|
||||
sudo $(PWD)/sower &
|
||||
@sleep 1
|
||||
curl 127.0.0.1
|
||||
@sleep 1
|
||||
@sudo pkill -9 sower || true
|
||||
|
||||
@@ -1,95 +1,90 @@
|
||||
# sower
|
||||
[](https://github.com/wweir/sower/releases)
|
||||
[](https://circleci.com/gh/wweir/sower)
|
||||
[](https://github.com/wweir/sower/actions)
|
||||
[](https://hub.docker.com/r/wweir/sower)
|
||||
[](https://github.com/wweir/sower/issues)
|
||||
[](https://github.com/wweir/sower/stargazers)
|
||||
[](LICENSE)
|
||||
|
||||
|
||||
中文介绍见 [Wiki](https://github.com/wweir/sower/wiki)
|
||||
|
||||
The sower is a cross-platform intelligent transparent proxy tool base on DNS solution.
|
||||
The sower is a cross-platform intelligent transparent proxy tool.
|
||||
|
||||
The first time you visit a new website, sower will detect if the domain in block list and add it in suggect list. So that, you do not need to care about the rules, sower will handle it in a intelligent way.
|
||||
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.
|
||||
|
||||
If you wanna enjoy the sower, you need to deploy sower on both server and client side.
|
||||
On client side, sower listening UDP `53` and TCP `80`/`443` ports, so that you need run it with privileged.
|
||||
On server side, it just listening to a port (default `5533`), parse and relay the request to target server.
|
||||
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.
|
||||
|
||||
Sower also provides an http(s) proxy listening on `:8080` by default. You can turn it off or use another port at any time.
|
||||
You can enjoy it by setting http_proxy or your DNS without any other settings.
|
||||
|
||||
If you already have another proxy solution, you can use it's socks5(h) service as a parent proxy to enjoy the sower's intelligent router.
|
||||
|
||||
|
||||
## Installation
|
||||
After Deployed, please check your config file, it is placed in `/usr/local/etc/sower.toml` by default. Here is the example config file [**conf/sower.toml**](https://github.com/wweir/sower/blob/master/conf/sower.toml)
|
||||
To enjoy the sower, you need to deploy sower on both server-side and client-side.
|
||||
|
||||
### Auto deploy
|
||||
Auto deploy script support Linux server side and masOS/Linux client side.
|
||||
The installation script has been integrated into the sower. You can install sower as system service by running `./sower -install 'xxx'` in terminal on the three most popular operating system platforms: Linux / Windows and masOS.
|
||||
|
||||
```shell
|
||||
$ bash -c "$(curl -s https://raw.githubusercontent.com/wweir/sower/master/deploy/install)"
|
||||
## Server
|
||||
*If you already have another proxy solution with socks5h support, you can skip server-side.*
|
||||
|
||||
At the server-side, the sower runs just like a web server proxy.
|
||||
It redirects HTTP requests to HTTPS and proxy https requests to the upstream HTTP service.
|
||||
You can use your certificate or use the auto-generated certificate by the sower.
|
||||
|
||||
What you must set is the upstream HTTP service. You can set it by parameter `-s`, eg:
|
||||
``` shell
|
||||
# sower -s 127.0.0.1:8080
|
||||
```
|
||||
|
||||
Then modify the configuration file as needed and set `127.0.0.1` as your first domain name server.
|
||||
In most situation, you just need to modify `/etc/resolv.conf`.
|
||||
## 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.
|
||||
|
||||
If you wanna uninstall sower, change `install` into `uninstall` and rerun the command.
|
||||
There are 3 kinds of proxy solutions, they are HTTP(S)_PROXY / DNS-based proxy / port-forward.
|
||||
|
||||
### Manually deploy
|
||||
1. Download the precompiled file from https://github.com/wweir/sower/releases
|
||||
2. Decompression the file into a folder
|
||||
3. Run `./sower -h` for help
|
||||
5. Config domain name server
|
||||
4. Config auto start
|
||||
### HTTP(S)_PROXY
|
||||
An HTTP(S)_PROXY listening on `:8080` is set by default if you run sower as client mode.
|
||||
|
||||
### Docker deploy
|
||||
The auto build docker images are [wweir/sower](https://hub.docker.com/r/wweir/sower).
|
||||
### DNS-based proxy
|
||||
**DNS-based** solution is not recommanded now, for incompatible with TLS 1.3 esni extention. DNS flush logic has been dropped, you should flush your dns cache manually
|
||||
in Windows and macOS.
|
||||
|
||||
It is very simple to use it on the server side. Export the port(5533) and run it directly.
|
||||
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.
|
||||
|
||||
But the client is more troublesome and needs some understanding of the working mechanism of the sower.
|
||||
If you want to enjoy the full experience provided by the sower, you can take sower as your private DNS on a long-running server and set it as your default DNS in your router.
|
||||
|
||||
### port-forward
|
||||
The port-forward can be only setted in configuration file, you can set it in section `client.port_mapping`, eg:
|
||||
``` toml
|
||||
[client.port_mapping]
|
||||
":2222"="aa.bb.cc:22"
|
||||
```
|
||||
|
||||
|
||||
## Architecture
|
||||
```
|
||||
request target servers
|
||||
<-------------+ +------------->
|
||||
| |
|
||||
| |
|
||||
+------------server-------------+
|
||||
| | relay service| |
|
||||
| +-----+---------------------+ |
|
||||
| | | |
|
||||
| | parse http(s) target url | |
|
||||
| | | |
|
||||
| +---------------------------+ |
|
||||
| shadow service |
|
||||
+--------^----------------------+
|
||||
| request domain server
|
||||
quic / KCP / TCP +---------->
|
||||
| |
|
||||
+--------+---client+------+-----+
|
||||
| | |
|
||||
| shadow service | |
|
||||
| relay service | dns |
|
||||
| | service |
|
||||
| | |
|
||||
| 127.0.0.1 or other |
|
||||
| | |
|
||||
+-^-----^----------+---^----^---+
|
||||
| | | |
|
||||
| | | | +----->
|
||||
http(s) proxy | +----------+ | |
|
||||
2 1 1 2
|
||||
+ + + +
|
||||
blocked request normal request
|
||||
|
||||
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
|
||||
```
|
||||
For more detail, see [透明代理 Sower 技术剖析](https://wweir.cc/post/%E9%80%8F%E6%98%8E%E4%BB%A3%E7%90%86-sower-%E6%8A%80%E6%9C%AF%E5%89%96%E6%9E%90/)
|
||||
|
||||
|
||||
## Todo
|
||||
- [x] authenticate
|
||||
- [ ] broker
|
||||
- [x] CI/CD
|
||||
- [ ] relay optimization
|
||||
- [ ] deploy script for all normal platform
|
||||
- [x] dns rule intelligent suggestions
|
||||
|
||||
+167
-111
@@ -1,140 +1,196 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"context"
|
||||
"bufio"
|
||||
"flag"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
toml "github.com/pelletier/go-toml"
|
||||
"github.com/wweir/fsnotify"
|
||||
"github.com/wweir/sower/util"
|
||||
"github.com/wweir/util-go/log"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
var Conf = struct {
|
||||
ConfigFile string
|
||||
NetType string `toml:"net_type"`
|
||||
Cipher string `toml:"cipher"`
|
||||
Password string `toml:"password"`
|
||||
type Client struct {
|
||||
Address string `toml:"address"`
|
||||
DNSUpstream string `toml:"dns_upstream"`
|
||||
Socks5Proxy string `toml:"socks5"`
|
||||
HTTPProxy string `toml:"http_proxy"`
|
||||
PortForward map[string]string `toml:"port_forward"`
|
||||
|
||||
ServerPort string `toml:"server_port"`
|
||||
ServerAddr string `toml:"server_addr"`
|
||||
HTTPProxy string `toml:"http_proxy"`
|
||||
|
||||
DNSServer string `toml:"dns_server"`
|
||||
ClientIP string `toml:"client_ip"`
|
||||
ClearDNSCache string `toml:"clear_dns_cache"`
|
||||
|
||||
BlockList []string `toml:"blocklist"`
|
||||
WhiteList []string `toml:"whitelist"`
|
||||
Suggestions []string `toml:"suggestions"`
|
||||
Verbose int `toml:"verbose"`
|
||||
}{}
|
||||
var mu = &sync.Mutex{}
|
||||
|
||||
var OnRefreash = []func() error{
|
||||
func() (err error) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
f, err := os.OpenFile(Conf.ConfigFile, os.O_RDONLY, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
file := Conf.ConfigFile
|
||||
if err = toml.NewDecoder(f).Decode(&Conf); err != nil {
|
||||
return err
|
||||
}
|
||||
Conf.ConfigFile = file
|
||||
|
||||
return flag.Set("v", strconv.Itoa(Conf.Verbose))
|
||||
},
|
||||
func() error {
|
||||
if Conf.ClearDNSCache != "" {
|
||||
ctx, _ := context.WithTimeout(context.TODO(), 5*time.Second)
|
||||
if err := exec.CommandContext(ctx, "sh", "-c", Conf.ClearDNSCache).Run(); err != nil {
|
||||
glog.Errorln(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Router struct {
|
||||
DetectLevel int `toml:"detect_level"`
|
||||
ProxyList []string `toml:"proxy_list"`
|
||||
ProxyRefs []string `toml:"proxy_refs"`
|
||||
DirectList []string `toml:"direct_list"`
|
||||
DirectRefs []string `toml:"direct_refs"`
|
||||
BlockList []string `toml:"block_list"`
|
||||
BlockRefs []string `toml:"block_refs"`
|
||||
} `toml:"router"`
|
||||
}
|
||||
type Server struct {
|
||||
Upstream string `toml:"upstream"`
|
||||
CertFile string `toml:"cert_file"`
|
||||
KeyFile string `toml:"key_file"`
|
||||
CertEmail string `toml:"cert_email"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
initArgs()
|
||||
var (
|
||||
version, date string
|
||||
|
||||
if _, err := os.Stat(Conf.ConfigFile); os.IsNotExist(err) {
|
||||
glog.Warningln("no config file has been load:", Conf.ConfigFile)
|
||||
return
|
||||
execFile, _ = os.Executable()
|
||||
execDir, _ = filepath.Abs(filepath.Dir(execFile))
|
||||
// Conf full config, include common and server / client
|
||||
conf = struct {
|
||||
file string
|
||||
Password string `toml:"password"`
|
||||
Client Client `toml:"client"`
|
||||
Server Server `toml:"server"`
|
||||
}{}
|
||||
)
|
||||
|
||||
func Init() (*Client, *Server, string) {
|
||||
beforeInitFlag()
|
||||
defer afterInitFlag()
|
||||
flag.StringVar(&conf.Password, "password", "", "password")
|
||||
flag.StringVar(&conf.Server.Upstream, "s", "", "upstream http service, eg: 127.0.0.1:8080")
|
||||
flag.StringVar(&conf.Server.CertFile, "s_cert", "", "tls cert file, gen cert from letsencrypt if empty")
|
||||
flag.StringVar(&conf.Server.KeyFile, "s_key", "", "tls key file, gen cert from letsencrypt if empty")
|
||||
flag.StringVar(&conf.Client.Address, "c", "", "remote server domain, eg: aa.bb.cc, socks5h://127.0.0.1:1080")
|
||||
flag.StringVar(&conf.Client.HTTPProxy, "http_proxy", ":8080", "http proxy, empty to disable")
|
||||
|
||||
if !flag.Parsed() {
|
||||
flag.Parse()
|
||||
}
|
||||
for i := range OnRefreash {
|
||||
if err := OnRefreash[i](); err != nil {
|
||||
glog.Fatalln(err)
|
||||
|
||||
defer log.Infow("starting", "version", version, "date", date, "config", &conf)
|
||||
if conf.file == "" {
|
||||
return &conf.Client, &conf.Server, conf.Password
|
||||
}
|
||||
|
||||
for i := range loadConfigFns {
|
||||
if err := loadConfigFns[i].fn(); err != nil {
|
||||
log.Fatalw("load config", "config", conf.file, "step", loadConfigFns[i].step, "err", err)
|
||||
}
|
||||
}
|
||||
watchConfigFile()
|
||||
return &conf.Client, &conf.Server, conf.Password
|
||||
}
|
||||
|
||||
func watchConfigFile() {
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
// refreshFns will be executed while init and write new config
|
||||
var loadConfigFns = []struct {
|
||||
step string
|
||||
fn func() error
|
||||
}{{"parse file", func() error {
|
||||
f, err := os.OpenFile(conf.file, os.O_RDONLY, 0644)
|
||||
if err != nil {
|
||||
glog.Fatalln(err)
|
||||
}
|
||||
if err := watcher.Add(filepath.Dir(Conf.ConfigFile)); err != nil {
|
||||
glog.Fatalln(err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case event := <-watcher.Events:
|
||||
if event.Op == fsnotify.Rename {
|
||||
if err := watcher.Add(Conf.ConfigFile); err != nil {
|
||||
glog.Errorln(err)
|
||||
}
|
||||
}
|
||||
|
||||
glog.V(1).Infof("watch %s event: %v", Conf.ConfigFile, event)
|
||||
for i := range OnRefreash {
|
||||
if err := OnRefreash[i](); err != nil {
|
||||
glog.Errorln(err)
|
||||
}
|
||||
}
|
||||
case err := <-watcher.Errors:
|
||||
glog.Fatalln(err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func AddSuggest(domain string) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
Conf.Suggestions = append(Conf.Suggestions, domain)
|
||||
Conf.Suggestions = util.NewReverseSecSlice(Conf.Suggestions).Sort().Uniq()
|
||||
|
||||
// safe write
|
||||
f, err := os.OpenFile(Conf.ConfigFile+"~", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
||||
if err != nil {
|
||||
glog.Errorln(err)
|
||||
return
|
||||
return xerrors.New(err.Error())
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if err := toml.NewEncoder(f).ArraysWithOneElementPerLine(true).Encode(Conf); err != nil {
|
||||
glog.Errorln(err)
|
||||
return
|
||||
return toml.NewDecoder(f).Decode(&conf)
|
||||
}}, {"load referenced rule", func() error {
|
||||
for _, addr := range conf.Client.Router.BlockRefs {
|
||||
lines, err := getRemoteRuleLines(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conf.Client.Router.BlockList = append(conf.Client.Router.BlockList, lines...)
|
||||
}
|
||||
for _, addr := range conf.Client.Router.ProxyRefs {
|
||||
lines, err := getRemoteRuleLines(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conf.Client.Router.ProxyList = append(conf.Client.Router.ProxyList, lines...)
|
||||
}
|
||||
for _, addr := range conf.Client.Router.DirectRefs {
|
||||
lines, err := getRemoteRuleLines(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conf.Client.Router.DirectList = append(conf.Client.Router.DirectList, lines...)
|
||||
}
|
||||
|
||||
if err = os.Rename(Conf.ConfigFile+"~", Conf.ConfigFile); err != nil {
|
||||
glog.Errorln(err)
|
||||
return nil
|
||||
}}}
|
||||
|
||||
func getRemoteRuleLines(addr string) ([]string, error) {
|
||||
resp, err := http.Get(addr)
|
||||
if err != nil {
|
||||
return nil, xerrors.New(err.Error())
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
br := bufio.NewReader(resp.Body)
|
||||
lines := []string{}
|
||||
for {
|
||||
line, _, err := br.ReadLine()
|
||||
if err == io.EOF {
|
||||
return lines, nil
|
||||
} else if err != nil {
|
||||
return nil, xerrors.New(err.Error())
|
||||
}
|
||||
|
||||
lines = append(lines, "**."+strings.TrimSpace(string(line)))
|
||||
}
|
||||
}
|
||||
|
||||
// flushCh to avoid parallel persist
|
||||
var flushCh = make(chan struct{})
|
||||
var flushOnce = sync.Once{}
|
||||
|
||||
// PersistRule persist rule into config file
|
||||
func PersistRule(domain string) {
|
||||
flushOnce.Do(func() {
|
||||
go flushConfDaemon()
|
||||
})
|
||||
|
||||
log.Infow("persist direct rule into config", "domain", domain)
|
||||
conf.Client.Router.DirectList = append(conf.Client.Router.DirectList, domain)
|
||||
select {
|
||||
case flushCh <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
func flushConfDaemon() {
|
||||
for range flushCh {
|
||||
// safe write file
|
||||
if conf.file != "" {
|
||||
f, err := os.OpenFile(conf.file+"~", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
||||
if err != nil {
|
||||
log.Errorw("flush config", "step", "flush", "err", err)
|
||||
continue
|
||||
}
|
||||
|
||||
conf.Client.Router.DirectList =
|
||||
util.NewReverseSecSlice(conf.Client.Router.DirectList).Sort().Uniq()
|
||||
|
||||
if err := toml.NewEncoder(f).ArraysWithOneElementPerLine(true).Encode(&conf); err != nil {
|
||||
log.Errorw("flush config", "step", "flush", "err", err)
|
||||
f.Close()
|
||||
continue
|
||||
}
|
||||
f.Close()
|
||||
|
||||
if stat, err := os.Stat(conf.file); err != nil {
|
||||
log.Warnw("get file stat", "file", conf.file, "err", err)
|
||||
} else {
|
||||
// There is no common way to transfer ownership for a file
|
||||
// cross-platform. Drop the ownership support but file mod.
|
||||
if err = os.Chmod(conf.file+"~", stat.Mode()); err != nil {
|
||||
log.Warnw("set file mod", "file", conf.file+"~", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err = os.Rename(conf.file+"~", conf.file); err != nil {
|
||||
log.Errorw("flush config", "step", "flush", "err", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
// +build darwin
|
||||
|
||||
package conf
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/wweir/util-go/log"
|
||||
)
|
||||
|
||||
const svcPath = "/Library/LaunchDaemons/sower.plist"
|
||||
const svcFile = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>sower</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/bin/sh</string>
|
||||
<string>-c</string>
|
||||
<string>%s %s</string>
|
||||
</array>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>`
|
||||
|
||||
var (
|
||||
ConfigDir = ""
|
||||
installCmd = ""
|
||||
uninstallFlag = false
|
||||
)
|
||||
|
||||
func beforeInitFlag() {
|
||||
if _, err := os.Stat(execDir + "/sower.toml"); err == nil {
|
||||
ConfigDir = execDir
|
||||
} else {
|
||||
dir, _ := os.UserConfigDir()
|
||||
ConfigDir = filepath.Join("/", dir, "sower")
|
||||
}
|
||||
|
||||
if _, err := os.Stat(ConfigDir + "/sower.toml"); err != nil {
|
||||
flag.StringVar(&conf.file, "f", "", "config file, rewrite all other parameters if set")
|
||||
} else {
|
||||
flag.StringVar(&conf.file, "f", ConfigDir+"/sower.toml", "config file, rewrite all other parameters if set")
|
||||
}
|
||||
|
||||
flag.StringVar(&installCmd, "install", "", "install service with cmd, eg: '-f \""+ConfigDir+"/sower.toml\"'")
|
||||
flag.BoolVar(&uninstallFlag, "uninstall", false, "uninstall service")
|
||||
}
|
||||
|
||||
func afterInitFlag() {
|
||||
switch {
|
||||
case installCmd != "":
|
||||
install()
|
||||
case uninstallFlag:
|
||||
uninstall()
|
||||
default:
|
||||
return
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func install() {
|
||||
if err := ioutil.WriteFile(svcPath, []byte(fmt.Sprintf(svcFile, execFile, installCmd)), 0644); err != nil {
|
||||
log.Fatalw("write service file", "err", err)
|
||||
}
|
||||
|
||||
execute("launchctl unload " + svcPath)
|
||||
if err := execute("launchctl load -wF " + svcPath); err != nil {
|
||||
log.Fatalw("install service", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
func uninstall() {
|
||||
execute("launchctl unload " + svcPath)
|
||||
os.Remove(svcPath)
|
||||
os.RemoveAll("/etc/sower")
|
||||
}
|
||||
|
||||
func execute(cmd string) error {
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
out, err := exec.CommandContext(ctx, "sh", "-c", cmd).CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cmd: %s, err: %s, output: %s", cmd, err, out)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
// +build linux
|
||||
|
||||
package conf
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/wweir/util-go/log"
|
||||
)
|
||||
|
||||
const svcPath = "/etc/systemd/system/sower.service"
|
||||
const svcFile = `[Unit]
|
||||
Description=Sower client service
|
||||
After=network.target
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
WorkingDirectory=/tmp
|
||||
ExecStart=%s %s
|
||||
RestartSec=3
|
||||
Restart=on-failure`
|
||||
|
||||
var (
|
||||
ConfigDir = ""
|
||||
installCmd = ""
|
||||
uninstallFlag = false
|
||||
)
|
||||
|
||||
func beforeInitFlag() {
|
||||
if _, err := os.Stat(execDir + "/sower.toml"); err == nil {
|
||||
ConfigDir = execDir
|
||||
|
||||
} else if stat, err := os.Stat("/etc/sower"); err == nil && stat.IsDir() {
|
||||
ConfigDir = "/etc/sower"
|
||||
|
||||
} else {
|
||||
dir, _ := os.UserConfigDir()
|
||||
ConfigDir = filepath.Join("/", dir, "sower")
|
||||
}
|
||||
|
||||
if _, err := os.Stat(ConfigDir + "/sower.toml"); err != nil {
|
||||
flag.StringVar(&conf.file, "f", "", "config file, rewrite all other parameters if set")
|
||||
} else {
|
||||
flag.StringVar(&conf.file, "f", ConfigDir+"/sower.toml", "config file, rewrite all other parameters if set")
|
||||
}
|
||||
|
||||
flag.StringVar(&installCmd, "install", "", "install service with cmd, eg: '-f "+ConfigDir+"/sower.toml'")
|
||||
flag.BoolVar(&uninstallFlag, "uninstall", false, "uninstall service")
|
||||
}
|
||||
|
||||
func afterInitFlag() {
|
||||
switch {
|
||||
case installCmd != "":
|
||||
install()
|
||||
case uninstallFlag:
|
||||
uninstall()
|
||||
default:
|
||||
return
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func install() {
|
||||
if err := ioutil.WriteFile(svcPath, []byte(fmt.Sprintf(svcFile, execFile, installCmd)), 0644); err != nil {
|
||||
log.Fatalw("write service file", "err", err)
|
||||
}
|
||||
if err := execute("systemctl daemon-reload"); err != nil {
|
||||
log.Fatalw("install service", "err", err)
|
||||
}
|
||||
if err := execute("systemctl enable sower"); err != nil {
|
||||
log.Fatalw("install service", "err", err)
|
||||
}
|
||||
if err := execute("systemctl start sower"); err != nil {
|
||||
log.Fatalw("install service", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
func uninstall() {
|
||||
execute("systemctl stop sower")
|
||||
execute("systemctl disable sower")
|
||||
os.Remove(svcPath)
|
||||
os.RemoveAll("/etc/sower")
|
||||
}
|
||||
|
||||
func execute(cmd string) error {
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
out, err := exec.CommandContext(ctx, "sh", "-c", cmd).CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cmd: %s, err: %s, output: %s", cmd, err, out)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
// +build !windows
|
||||
|
||||
package conf
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func initArgs() {
|
||||
flag.StringVar(&Conf.ConfigFile, "f", filepath.Dir(os.Args[0])+"/sower.toml", "config file location")
|
||||
flag.StringVar(&Conf.NetType, "n", "TCP", "proxy net type (QUIC|KCP|TCP)")
|
||||
flag.StringVar(&Conf.Cipher, "C", "AES_128_GCM", "cipher type (AES_128_GCM|AES_192_GCM|AES_256_GCM|CHACHA20_IETF_POLY1305|XCHACHA20_IETF_POLY1305)")
|
||||
flag.StringVar(&Conf.Password, "p", "12345678", "password")
|
||||
flag.StringVar(&Conf.ServerPort, "P", "5533", "server mode listen port")
|
||||
flag.StringVar(&Conf.ServerAddr, "s", "", "server IP (run in client mode if set)")
|
||||
flag.StringVar(&Conf.HTTPProxy, "H", "", "http proxy listen addr")
|
||||
flag.StringVar(&Conf.DNSServer, "d", "114.114.114.114", "client dns server")
|
||||
flag.StringVar(&Conf.ClientIP, "c", "127.0.0.1", "client dns service redirect IP")
|
||||
|
||||
if !flag.Parsed() {
|
||||
flag.Set("logtostderr", "true")
|
||||
flag.Parse()
|
||||
}
|
||||
}
|
||||
+109
-66
@@ -1,15 +1,19 @@
|
||||
// +build windows
|
||||
|
||||
package conf
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/wweir/util-go/log"
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.org/x/sys/windows/svc"
|
||||
"golang.org/x/sys/windows/svc/eventlog"
|
||||
@@ -19,73 +23,85 @@ import (
|
||||
const name = "sower"
|
||||
const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown | svc.AcceptPauseAndContinue
|
||||
|
||||
func initArgs() {
|
||||
flag.StringVar(&Conf.ConfigFile, "f", filepath.Dir(os.Args[0])+"/sower.toml", "config file location")
|
||||
install := flag.Bool("install", false, "install sower as a service")
|
||||
uninstall := flag.Bool("uninstall", false, "uninstall sower from service list")
|
||||
var (
|
||||
ConfigDir = ""
|
||||
installCmd = false
|
||||
uninstallFlag = false
|
||||
)
|
||||
|
||||
if !flag.Parsed() {
|
||||
flag.Set("log_dir", filepath.Dir(os.Args[0]))
|
||||
flag.Parse()
|
||||
}
|
||||
func beforeInitFlag() {
|
||||
flag.StringVar(&conf.file, "f", filepath.Join(execDir, "sower.toml"), "config file, rewrite all other parameters if set")
|
||||
flag.BoolVar(&installCmd, "install", false, "put any character to install as a service, eg: true")
|
||||
}
|
||||
|
||||
func afterInitFlag() {
|
||||
switch {
|
||||
case *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, os.Args[0], 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()
|
||||
})
|
||||
os.Exit(0)
|
||||
|
||||
case *uninstall:
|
||||
serviceDo(func(s *mgr.Service) error {
|
||||
err := s.Delete()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return eventlog.Remove(name)
|
||||
})
|
||||
os.Exit(0)
|
||||
|
||||
case installCmd:
|
||||
install()
|
||||
case uninstallFlag:
|
||||
uninstall()
|
||||
default:
|
||||
os.Chdir(filepath.Dir(os.Args[0]))
|
||||
if active, err := svc.IsAnInteractiveSession(); err != nil {
|
||||
glog.Exitf("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 {
|
||||
glog.Exitln(err)
|
||||
}
|
||||
defer elog.Close()
|
||||
|
||||
if err := svc.Run(name, &myservice{}); err != nil {
|
||||
elog.Error(1, fmt.Sprintf("%s service failed: %v", name, err))
|
||||
glog.Exitln(err)
|
||||
}
|
||||
elog.Info(1, fmt.Sprintf("winsvc.RunAsService: %s service stopped", name))
|
||||
os.Exit(0)
|
||||
}()
|
||||
}
|
||||
runAsService()
|
||||
return
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func runAsService() {
|
||||
os.Chdir(filepath.Dir(os.Args[0]))
|
||||
if active, err := svc.IsAnInteractiveSession(); err != nil {
|
||||
log.Fatalw("failed to determine if we are running in an interactive session", "err", 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) {
|
||||
@@ -101,12 +117,12 @@ func serviceDo(fn func(*mgr.Service) error) {
|
||||
func mgrDo(fn func(m *mgr.Mgr) error) {
|
||||
m, err := mgr.Connect()
|
||||
if err != nil {
|
||||
glog.Exitln(err)
|
||||
log.Fatalw("install service", "err", err)
|
||||
}
|
||||
defer m.Disconnect()
|
||||
|
||||
if err := fn(m); err != nil {
|
||||
glog.Fatalln(err)
|
||||
log.Fatalw("install service", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +131,7 @@ 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 {
|
||||
glog.Errorln(err)
|
||||
log.Errorw("install service", "err", err)
|
||||
return
|
||||
}
|
||||
defer elog.Close()
|
||||
@@ -143,3 +159,30 @@ func (m *myservice) Execute(args []string, r <-chan svc.ChangeRequest, changes 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
|
||||
}
|
||||
|
||||
+65
-44
@@ -1,44 +1,65 @@
|
||||
net_type="TCP"
|
||||
cipher="AES_128_GCM"
|
||||
password="12345678"
|
||||
server_port="5533"
|
||||
# server_addr="remote-server:5533" # replce it to remote server
|
||||
http_proxy=":8080" # eg: 192.168.0.2:8080
|
||||
dns_server="" # Keep empty for dynamic setting from net env
|
||||
client_ip="127.0.0.1" # listen the IP, dns target is the IP
|
||||
# clear_dns_cache="killall -HUP mDNSResponder"
|
||||
blocklist=[
|
||||
"**.google.com", # google
|
||||
"**.goo.gl",
|
||||
"**.googleusercontent.com",
|
||||
"**.googleapis.com",
|
||||
"*.googlesource.com",
|
||||
"**.youtube.com", # youtube
|
||||
"**.ytimg.com",
|
||||
"**.ggpht.com",
|
||||
"**.googlevideo.com",
|
||||
"**.facebook.com", # facebook
|
||||
"**.fbcdn.net",
|
||||
"**.twitter.com", # twitter
|
||||
"**.twimg.com",
|
||||
"**.blogspot.com", # blogspot
|
||||
"**.appspot.com",
|
||||
"**.wikipedia.org", # wikipeida
|
||||
"*.cloudfront.net",
|
||||
"**.amazon.com",
|
||||
"**.amazonaws.com",
|
||||
"*.githubusercontent.com",
|
||||
"*.github.com",
|
||||
"*.github.io",
|
||||
"**.mozilla.com",
|
||||
"**.mozilla.net",
|
||||
"**.akadns.net",
|
||||
]
|
||||
whitelist=[
|
||||
"iamp.*.*.*",
|
||||
"iamp.*.*",
|
||||
"imap-mail.outlook.com",
|
||||
"**.qq.com",
|
||||
"**.baidu.com",
|
||||
]
|
||||
verbose=0
|
||||
password = "" # sower password
|
||||
|
||||
[client]
|
||||
address = "" # aa.bb.cc, socks5h://127.0.0.1:1080
|
||||
dns_upstream = "" # keep empty to set via dhcp, not effective in any environment
|
||||
http_proxy = ""
|
||||
socks5 = ""
|
||||
|
||||
[client.port_forward]
|
||||
# eg: ":2222"="aa.bb.cc:22"
|
||||
|
||||
[client.router]
|
||||
block_refs = [
|
||||
"https://cdn.jsdelivr.net/gh/pexcn/daily@gh-pages/adlist/adlist.txt",
|
||||
]
|
||||
detect_level = 0 # [-4, 4], the bigger the harder to add
|
||||
direct_cird_refs = [
|
||||
"https://cdn.jsdelivr.net/gh/pexcn/daily@gh-pages/chnroute/chnroute.txt",
|
||||
]
|
||||
direct_list = [
|
||||
"**.in-addr.arpa",
|
||||
"imap.*.*",
|
||||
"imap.*.*.*",
|
||||
"smtp.*.*",
|
||||
"smtp.*.*.*",
|
||||
"pop.*.*",
|
||||
"pop.*.*.*",
|
||||
"**.cn",
|
||||
]
|
||||
direct_refs = [
|
||||
"https://cdn.jsdelivr.net/gh/pexcn/daily@gh-pages/chinalist/chinalist.txt",
|
||||
]
|
||||
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.*",
|
||||
]
|
||||
proxy_refs = [
|
||||
"https://cdn.jsdelivr.net/gh/pexcn/daily@gh-pages/gfwlist/gfwlist.txt",
|
||||
]
|
||||
|
||||
[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
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
# Compile
|
||||
FROM golang:1.11-alpine AS compiler
|
||||
|
||||
RUN apk add --no-cache git make
|
||||
|
||||
# enable go modules
|
||||
WORKDIR /go-mod
|
||||
COPY . .
|
||||
|
||||
# do not worry about downloading dependency, sower will fix this.
|
||||
RUN CGO_ENABLED=0 make build
|
||||
|
||||
|
||||
# Build image
|
||||
FROM scratch
|
||||
|
||||
COPY --from=compiler /go-mod/sower /sower
|
||||
ENTRYPOINT [ "/sower" ]
|
||||
@@ -1,18 +0,0 @@
|
||||
<?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>cc.wweir.sower</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/usr/local/bin/sower</string>
|
||||
<string>-f</string>
|
||||
<string>/usr/local/etc/sower.toml</string>
|
||||
</array>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,93 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -u -e
|
||||
[[ -d /tmp/sower ]] || mkdir /tmp/sower
|
||||
cd /tmp/sower
|
||||
|
||||
echo ======== Install sower to your system ========
|
||||
succ_message(){
|
||||
echo
|
||||
echo ======== Installed sower to your system ========
|
||||
echo The config file is: /usr/local/etc/sower.toml
|
||||
echo please set 127.0.0.1 as the first domain name server manually.
|
||||
}
|
||||
|
||||
# main logic
|
||||
VERSION="$(curl -s "https://api.github.com/repos/wweir/sower/releases/latest" | awk -F '"' '/tag_name/{printf $4}')"
|
||||
case "$(uname -s)" in
|
||||
"Darwin")
|
||||
printf "Please enter remote server adddress: "
|
||||
read ADDRESS
|
||||
echo
|
||||
|
||||
curl -SLf https://github.com/wweir/sower/releases/download/$VERSION/sower_darwin_amd64.tar.gz | tar xzv
|
||||
sudo mkdir -p /usr/local/bin/
|
||||
sudo mv sower /usr/local/bin/
|
||||
|
||||
if [[ -r /usr/local/etc/sower.toml ]]; then
|
||||
echo The config file already exists, keep the original file
|
||||
else
|
||||
sed -i~ "s/# server_addr=\"remote-server/server_addr=\"$ADDRESS/" sower.toml
|
||||
sed -i~ "s/# clear_dns_cache/clear_dns_cache/" sower.toml
|
||||
sudo mkdir -p /usr/local/etc/
|
||||
sudo mv sower.toml /usr/local/etc/
|
||||
fi
|
||||
|
||||
echo
|
||||
echo Register auto start service, root privilege is needed!
|
||||
echo
|
||||
sudo mv cc.wweir.sower.plist /Library/LaunchDaemons/
|
||||
sudo chown root:wheel /Library/LaunchDaemons/cc.wweir.sower.plist
|
||||
sudo launchctl load -w /Library/LaunchDaemons/cc.wweir.sower.plist
|
||||
succ_message
|
||||
;;
|
||||
|
||||
"Linux")
|
||||
if [[ "$(cat /proc/1/comm)" != "systemd" ]]; then
|
||||
echo do not support auto deploy on SysVinit
|
||||
exit 1
|
||||
fi
|
||||
printf "Server side or client side, which do you wanna install [c/s]: "
|
||||
read SIDE
|
||||
echo
|
||||
|
||||
case "$SIDE" in
|
||||
"c")
|
||||
printf "Please enter remote server adddress: "
|
||||
read ADDRESS
|
||||
echo
|
||||
|
||||
curl -SLf https://github.com/wweir/sower/releases/download/$VERSION/sower_linux_amd64.tar.gz | tar xzv
|
||||
|
||||
sudo mv sower /usr/local/bin/
|
||||
|
||||
if [[ -r /usr/local/etc/sower.toml ]]; then
|
||||
echo The config file already exists, keep the original file
|
||||
else
|
||||
sed -i~ "s/# server_addr=\"remote-server/server_addr=\"$ADDRESS/" sower.toml
|
||||
sudo mv sower.toml /usr/local/etc/
|
||||
fi
|
||||
|
||||
sudo mv sower-client.service /etc/systemd/system/
|
||||
sudo systemctl enable sower-client
|
||||
sudo systemctl start sower-client
|
||||
succ_message
|
||||
;;
|
||||
|
||||
"s")
|
||||
curl -SLf https://github.com/wweir/sower/releases/download/$VERSION/sower_linux_amd64.tar.gz | tar xzv
|
||||
|
||||
sudo mv sower /usr/local/bin/
|
||||
sudo mv sower-server.service /etc/systemd/system/
|
||||
sudo systemctl enable sower-server
|
||||
sudo systemctl start sower-server
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "invalid: $SIDE" && exit 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
echo Do not support auto deploy on this platform.
|
||||
;;
|
||||
esac
|
||||
@@ -1,14 +0,0 @@
|
||||
[Unit]
|
||||
Description=Sower client service
|
||||
After=network.target
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
WorkingDirectory=/tmp
|
||||
ExecStart=/usr/local/bin/sower -f /usr/local/etc/sower.toml
|
||||
RestartSec=3
|
||||
Restart=on-failure
|
||||
@@ -1,14 +0,0 @@
|
||||
[Unit]
|
||||
Description=Sower server service
|
||||
After=network.target
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=nobody
|
||||
WorkingDirectory=/tmp
|
||||
ExecStart=/usr/local/bin/sower -n TCP -v 1
|
||||
RestartSec=3
|
||||
Restart=on-failure
|
||||
@@ -1,20 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -u -x -e
|
||||
|
||||
# main logic
|
||||
case "$(uname -s)" in
|
||||
"Darwin")
|
||||
sudo launchctl unload /Library/LaunchDaemons/cc.wweir.sower.plist
|
||||
sudo rm -rf /Library/LaunchDaemons/cc.wweir.sower.plist
|
||||
rm -rf /usr/local/bin/sower /usr/local/etc/sower.toml
|
||||
;;
|
||||
"Linux")
|
||||
sudo systemctl disable sower-client ||true
|
||||
sudo systemctl disable sower-server || true
|
||||
sudo rm -rf /etc/systemd/system/sower-client.service /etc/systemd/system/sower-server.service
|
||||
sudo rm -rf /usr/local/bin/sower /usr/local/etc/sower.toml
|
||||
;;
|
||||
*)
|
||||
echo Do not support auto deploy on this platform.
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,59 @@
|
||||
package dhcp
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"net"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/krolaw/dhcp4"
|
||||
"github.com/libp2p/go-reuseport"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var xid = make([]byte, 4)
|
||||
var broadcastAddr, _ = net.ResolveUDPAddr("udp", "255.255.255.255:67")
|
||||
|
||||
func GetDefaultDNSServer() (string, error) {
|
||||
iface, err := PickInternetInterface()
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "pick interface")
|
||||
}
|
||||
|
||||
rand.Read(xid)
|
||||
pack := dhcp4.RequestPacket(dhcp4.Discover, iface.HardwareAddr, net.IPv4(0, 0, 0, 0), xid, true, []dhcp4.Option{
|
||||
{Code: dhcp4.OptionRequestedIPAddress, Value: []byte(iface.IP.To4())},
|
||||
{Code: dhcp4.End},
|
||||
})
|
||||
|
||||
var conn net.PacketConn
|
||||
if runtime.GOOS == "windows" {
|
||||
if conn, err = reuseport.ListenPacket("udp4", iface.IP.String()+":68"); err != nil {
|
||||
return "", errors.Wrap(err, "listen dhcp")
|
||||
}
|
||||
} else {
|
||||
if conn, err = reuseport.ListenPacket("udp4", "0.0.0.0:68"); err != nil {
|
||||
return "", errors.Wrap(err, "listen dhcp")
|
||||
}
|
||||
}
|
||||
defer conn.Close()
|
||||
conn.SetDeadline(time.Now().Add(3 * time.Second))
|
||||
|
||||
if _, err := conn.WriteTo([]byte(pack), broadcastAddr); err != nil {
|
||||
return "", errors.Wrap(err, "write broadcast")
|
||||
}
|
||||
|
||||
buf := make([]byte, 1500 /*MTU*/)
|
||||
n, _, err := conn.ReadFrom(buf)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "read dhcp offer")
|
||||
}
|
||||
|
||||
pack = dhcp4.Packet(buf[:n])
|
||||
dnsBytes := pack.ParseOptions()[dhcp4.OptionDomainNameServer]
|
||||
if len(dnsBytes) < 4 {
|
||||
return "", errors.New("no DNS setting in upstream network device")
|
||||
}
|
||||
|
||||
return net.IPv4(dnsBytes[0], dnsBytes[1], dnsBytes[2], dnsBytes[3]).String(), nil
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package dhcp_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/wweir/sower/dhcp"
|
||||
)
|
||||
|
||||
func Example_dns() {
|
||||
got, err := dhcp.GetDefaultDNSServer()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(got)
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// +build !windows
|
||||
|
||||
package dhcp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
)
|
||||
|
||||
// PickInternetInterface pick the first active net interface
|
||||
func PickInternetInterface() (*Iface, error) {
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := range ifaces {
|
||||
if len(ifaces[i].HardwareAddr) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
addrs, _ := ifaces[i].Addrs()
|
||||
for _, addr := range addrs {
|
||||
if ip := addr.(*net.IPNet).IP.To4(); ip != nil {
|
||||
return &Iface{ifaces[i].HardwareAddr, ip}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, errors.New("no valid interface")
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package dhcp_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/wweir/sower/dhcp"
|
||||
)
|
||||
|
||||
func Example_iface() {
|
||||
got, err := dhcp.PickInternetInterface()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(got)
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// +build windows
|
||||
|
||||
package dhcp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"net"
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// PickInternetInterface pick the first active net interface
|
||||
func PickInternetInterface() (*Iface, error) {
|
||||
list, err := getAdapterList()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for {
|
||||
if list.DhcpEnabled == 1 && list.LeaseObtained != 0 {
|
||||
ip := bytes.TrimRight(list.IpAddressList.IpAddress.String[:], string([]byte{0}))
|
||||
return &Iface{net.HardwareAddr(list.Address[:list.AddressLength]), net.ParseIP(string(ip))}, nil
|
||||
}
|
||||
|
||||
if list.Next != nil {
|
||||
list = list.Next
|
||||
} else {
|
||||
return nil, errors.New("no valid interface")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getAdapterList() (*syscall.IpAdapterInfo, error) {
|
||||
b := make([]byte, 1000)
|
||||
l := uint32(len(b))
|
||||
a := (*syscall.IpAdapterInfo)(unsafe.Pointer(&b[0]))
|
||||
// TODO(mikio): GetAdaptersInfo returns IP_ADAPTER_INFO that
|
||||
// contains IPv4 address list only. We should use another API
|
||||
// for fetching IPv6 stuff from the kernel.
|
||||
err := syscall.GetAdaptersInfo(a, &l)
|
||||
if err == syscall.ERROR_BUFFER_OVERFLOW {
|
||||
b = make([]byte, l)
|
||||
a = (*syscall.IpAdapterInfo)(unsafe.Pointer(&b[0]))
|
||||
err = syscall.GetAdaptersInfo(a, &l)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, os.NewSyscallError("GetAdaptersInfo", err)
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package dhcp
|
||||
|
||||
import "net"
|
||||
|
||||
// Iface is net interface address info
|
||||
type Iface struct {
|
||||
net.HardwareAddr
|
||||
net.IP
|
||||
}
|
||||
-91
@@ -1,91 +0,0 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"net"
|
||||
"runtime"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/wweir/netboot/dhcp4"
|
||||
)
|
||||
|
||||
var xid = make([]byte, 4)
|
||||
|
||||
func GetDefaultDNSServer() string {
|
||||
pack := &dhcp4.Packet{
|
||||
Type: dhcp4.MsgDiscover,
|
||||
Broadcast: true,
|
||||
}
|
||||
options := map[dhcp4.Option][]byte{
|
||||
dhcp4.OptRequestedOptions: []byte{byte(dhcp4.OptDNSServers)},
|
||||
}
|
||||
|
||||
NewConn := dhcp4.NewConn
|
||||
if runtime.GOOS == "linux" {
|
||||
NewConn = dhcp4.NewSnooperConn
|
||||
}
|
||||
|
||||
ifaces := mustGetInterfaces()
|
||||
for _, iface := range ifaces {
|
||||
conn, err := NewConn(iface.IP.String() + ":68")
|
||||
if err != nil { // maybe in use
|
||||
glog.V(1).Infoln(err)
|
||||
continue
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
rand.Read(xid)
|
||||
pack.TransactionID = xid
|
||||
pack.HardwareAddr = iface.Interface.HardwareAddr
|
||||
options[dhcp4.OptClientIdentifier] = iface.Interface.HardwareAddr
|
||||
pack.Options = dhcp4.Options(options)
|
||||
|
||||
if err := conn.SendDHCP(pack, iface.Interface); err != nil {
|
||||
glog.Errorln(err)
|
||||
continue
|
||||
}
|
||||
|
||||
pack, _, err = conn.RecvDHCP()
|
||||
if err != nil {
|
||||
glog.Errorln(err)
|
||||
continue
|
||||
}
|
||||
|
||||
ips, err := pack.Options.IPs(dhcp4.OptDNSServers)
|
||||
if err != nil {
|
||||
glog.Errorln(err)
|
||||
continue
|
||||
}
|
||||
return ips[0].String() // if len(ips) == 0, err should not be wrong size
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type netIface struct {
|
||||
*net.Interface
|
||||
net.Addr
|
||||
net.IP
|
||||
}
|
||||
|
||||
func mustGetInterfaces() []*netIface {
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
glog.Fatalln(err)
|
||||
}
|
||||
|
||||
v4Iface := make([]*netIface, 0, len(ifaces))
|
||||
for i := range ifaces {
|
||||
if len(ifaces[i].HardwareAddr) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
addrs, _ := ifaces[i].Addrs()
|
||||
for _, addr := range addrs {
|
||||
if ip := addr.(*net.IPNet).IP.To4(); ip != nil {
|
||||
v4Iface = append(v4Iface, &netIface{&ifaces[i], addr, ip})
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return v4Iface
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package dns
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestGetDefaultDNSServer(t *testing.T) {
|
||||
t.Skip("skip for some enviroment not support dhcp")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
}{{
|
||||
"",
|
||||
}}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := GetDefaultDNSServer(); got == "" {
|
||||
t.Errorf("GetDefaultDNSServer() return empty")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
-152
@@ -1,152 +0,0 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/miekg/dns"
|
||||
mem "github.com/wweir/mem-go"
|
||||
"github.com/wweir/sower/conf"
|
||||
)
|
||||
|
||||
const colon = byte(':')
|
||||
|
||||
func StartDNS(dnsServer, listenIP string) {
|
||||
ip := net.ParseIP(listenIP)
|
||||
|
||||
suggest := &intelliSuggest{listenIP, 800 * time.Millisecond}
|
||||
mem.DefaultCache = mem.New(time.Hour)
|
||||
|
||||
dhcpCh := make(chan struct{})
|
||||
if dnsServer != "" {
|
||||
dnsServer = net.JoinHostPort(dnsServer, "53")
|
||||
} else {
|
||||
go func() {
|
||||
for {
|
||||
<-dhcpCh
|
||||
host := GetDefaultDNSServer()
|
||||
if host == "" {
|
||||
continue
|
||||
}
|
||||
// atomic action
|
||||
dnsServer = net.JoinHostPort(host, "53")
|
||||
glog.Infoln("set dns server to", host)
|
||||
}
|
||||
}()
|
||||
dhcpCh <- struct{}{}
|
||||
}
|
||||
|
||||
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, colon); idx > 0 {
|
||||
domain = domain[:idx]
|
||||
}
|
||||
|
||||
matchAndServe(w, r, domain, listenIP, dnsServer, dhcpCh, ip, suggest)
|
||||
})
|
||||
|
||||
server := &dns.Server{Addr: net.JoinHostPort(listenIP, "53"), Net: "udp"}
|
||||
glog.Fatalln(server.ListenAndServe())
|
||||
}
|
||||
|
||||
func matchAndServe(w dns.ResponseWriter, r *dns.Msg, domain, listenIP, dnsServer string,
|
||||
dhcpCh chan struct{}, ipNet net.IP, suggest *intelliSuggest) {
|
||||
|
||||
inWriteList := whiteList.Match(domain)
|
||||
|
||||
if !inWriteList && (blockList.Match(domain) || suggestList.Match(domain)) {
|
||||
glog.V(2).Infof("match %s suss", domain)
|
||||
w.WriteMsg(localA(r, domain, ipNet))
|
||||
return
|
||||
}
|
||||
|
||||
if !inWriteList {
|
||||
go mem.Remember(suggest, domain)
|
||||
}
|
||||
|
||||
msg, err := dns.Exchange(r, dnsServer)
|
||||
if err != nil && dhcpCh != nil {
|
||||
select {
|
||||
case dhcpCh <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
if msg == nil { // expose any response except nil
|
||||
glog.V(1).Infof("get dns of %s fail: %s", domain, err)
|
||||
return
|
||||
}
|
||||
w.WriteMsg(msg)
|
||||
}
|
||||
|
||||
type intelliSuggest struct {
|
||||
listenIP string
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func (i *intelliSuggest) GetOne(domain interface{}) (iface interface{}, e error) {
|
||||
iface, e = struct{}{}, nil
|
||||
|
||||
// kill deadloop, for ugly wildcard setting dns setting
|
||||
addr := strings.TrimSuffix(domain.(string), ".")
|
||||
if len(strings.Split(addr, ".")) > 10 {
|
||||
return
|
||||
}
|
||||
|
||||
// give local dial a hand, make it not so easy to be added into suggestions
|
||||
HTTPPing(addr, addr, Http, i.timeout/20)
|
||||
|
||||
var (
|
||||
pings = []struct {
|
||||
viaAddr string
|
||||
port Port
|
||||
}{
|
||||
{addr, Http},
|
||||
{addr, Https},
|
||||
{i.listenIP, Http},
|
||||
{i.listenIP, Https},
|
||||
}
|
||||
half = int32(len(pings) / 2)
|
||||
score = new(int32)
|
||||
)
|
||||
for idx := range pings {
|
||||
go func(idx int) {
|
||||
err := HTTPPing(pings[idx].viaAddr, addr, pings[idx].port, i.timeout)
|
||||
if err != nil {
|
||||
// local ping fail
|
||||
if pings[idx].viaAddr == addr {
|
||||
atomic.AddInt32(score, 1)
|
||||
}
|
||||
} else {
|
||||
// remote ping succ
|
||||
if pings[idx].viaAddr == i.listenIP {
|
||||
atomic.AddInt32(score, 1)
|
||||
|
||||
// local ping took longger than remote
|
||||
} else if atomic.LoadInt32(score) >= half {
|
||||
atomic.AddInt32(score, 1)
|
||||
}
|
||||
}
|
||||
|
||||
if atomic.LoadInt32(score) > half {
|
||||
atomic.StoreInt32(score, 0) // avoid redo add action
|
||||
conf.AddSuggest(addr)
|
||||
glog.Infof("added suggest domain: %s", addr)
|
||||
}
|
||||
}(idx)
|
||||
}
|
||||
return
|
||||
}
|
||||
-57
@@ -1,57 +0,0 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/wweir/sower/conf"
|
||||
"github.com/wweir/sower/util"
|
||||
)
|
||||
|
||||
var (
|
||||
blockList *util.Node
|
||||
whiteList *util.Node
|
||||
suggestList *util.Node
|
||||
)
|
||||
|
||||
func init() {
|
||||
host, _, _ := net.SplitHostPort(conf.Conf.ServerAddr)
|
||||
|
||||
//first init
|
||||
blockList = loadRules("block", conf.Conf.BlockList)
|
||||
suggestList = loadRules("suggest", conf.Conf.Suggestions)
|
||||
whiteList = loadRules("white", conf.Conf.WhiteList)
|
||||
whiteList.Add(host)
|
||||
|
||||
conf.OnRefreash = append(conf.OnRefreash, func() error {
|
||||
blockList = loadRules("block", conf.Conf.BlockList)
|
||||
suggestList = loadRules("suggest", conf.Conf.Suggestions)
|
||||
whiteList = loadRules("white", conf.Conf.WhiteList)
|
||||
whiteList.Add(host)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func loadRules(name string, list []string) *util.Node {
|
||||
rule := util.NewNodeFromRules(".", list...)
|
||||
glog.V(3).Infof("load %s rule:\n%s", name, rule)
|
||||
return rule
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -1,27 +1,16 @@
|
||||
module github.com/wweir/sower
|
||||
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/bifurcation/mint v0.0.0-20181105071958-a14404e9a861 // indirect
|
||||
github.com/cheekybits/genny v1.0.0 // indirect
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
|
||||
github.com/hashicorp/golang-lru v0.5.0 // indirect
|
||||
github.com/klauspost/cpuid v1.2.0 // indirect
|
||||
github.com/klauspost/reedsolomon v1.9.0 // indirect
|
||||
github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f // indirect
|
||||
github.com/lucas-clemente/quic-go v0.10.1
|
||||
github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced // indirect
|
||||
github.com/miekg/dns v1.1.4
|
||||
github.com/pelletier/go-toml v1.2.0
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect
|
||||
github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b // indirect
|
||||
github.com/tjfoc/gmsm v1.0.1 // indirect
|
||||
github.com/ulule/deepcopier v0.0.0-20171107155558-ca99b135e50f // indirect
|
||||
github.com/wweir/fsnotify v1.4.8
|
||||
github.com/wweir/mem-go v0.0.0-20190109100331-8673ab596296
|
||||
github.com/wweir/netboot v0.0.2
|
||||
github.com/xtaci/kcp-go v5.0.7+incompatible
|
||||
golang.org/x/crypto v0.0.0-20190222235706-ffb98f73852f
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd // indirect
|
||||
golang.org/x/sys v0.0.0-20190222171317-cd391775e71e
|
||||
github.com/krolaw/dhcp4 v0.0.0-20190909130307-a50d88189771
|
||||
github.com/libp2p/go-reuseport v0.0.2
|
||||
github.com/miekg/dns v1.1.30
|
||||
github.com/pelletier/go-toml v1.8.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/wweir/util-go/log v0.0.0-20200701032032-3cff7b4a46ea
|
||||
github.com/wweir/util-go/mem v0.0.0-20200701032032-3cff7b4a46ea
|
||||
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543
|
||||
)
|
||||
|
||||
@@ -1,46 +1,75 @@
|
||||
github.com/bifurcation/mint v0.0.0-20181105071958-a14404e9a861 h1:x17NvoJaphEzay72TFej4OSSsgu3xRYBLkbIwdofS/4=
|
||||
github.com/bifurcation/mint v0.0.0-20181105071958-a14404e9a861/go.mod h1:zVt7zX3K/aDCk9Tj+VM7YymsX66ERvzCJzw8rFCX2JU=
|
||||
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
|
||||
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/klauspost/cpuid v1.2.0 h1:NMpwD2G9JSFOE1/TJjGSo5zG7Yb2bTe7eq1jH+irmeE=
|
||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/reedsolomon v1.9.0 h1:usyTY5K7D2B6WOHn2jmpB7ky8Qom96mShZmmq3OW4JU=
|
||||
github.com/klauspost/reedsolomon v1.9.0/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4=
|
||||
github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f h1:sSeNEkJrs+0F9TUau0CgWTTNEwF23HST3Eq0A+QIx+A=
|
||||
github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f/go.mod h1:JpH9J1c9oX6otFSgdUHwUBUizmKlrMjxWnIAjff4m04=
|
||||
github.com/lucas-clemente/quic-go v0.10.1 h1:ipcMmYP9RT+b1YytOKGUY1qndxPGOczVEQkAVz3CZrs=
|
||||
github.com/lucas-clemente/quic-go v0.10.1/go.mod h1:wuD+2XqEx8G9jtwx5ou2BEYBsE+whgQmlj0Vz/77PrY=
|
||||
github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced h1:zqEC1GJZFbGZA0tRyNZqRjep92K5fujFtFsu5ZW7Aug=
|
||||
github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced/go.mod h1:NCcRLrOTZbzhZvixZLlERbJtDtYsmMw8Jc4vS8Z0g58=
|
||||
github.com/miekg/dns v1.1.4 h1:rCMZsU2ScVSYcAsOXgmC6+AKOK+6pmQTOcw03nfwYV0=
|
||||
github.com/miekg/dns v1.1.4/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
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/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/krolaw/dhcp4 v0.0.0-20190909130307-a50d88189771 h1:t2c2B9g1ZVhMYduqmANSEGVD3/1WlsrEYNPtVoFlENk=
|
||||
github.com/krolaw/dhcp4 v0.0.0-20190909130307-a50d88189771/go.mod h1:0AqAH3ZogsCrvrtUpvc6EtVKbc3w6xwZhkvGLuqyi3o=
|
||||
github.com/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.30 h1:Qww6FseFn8PRfw07jueqIXqodm0JKiiKuK0DeXSqfyo=
|
||||
github.com/miekg/dns v1.1.30/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/pelletier/go-toml v1.8.0 h1:Keo9qb7iRJs2voHvunFtuuYFsbWeOBh8/P9v/kVMFtw=
|
||||
github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 h1:89CEmDvlq/F7SJEOqkIdNDGJXrQIhuIx9D2DBXjavSU=
|
||||
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
|
||||
github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b h1:mnG1fcsIB1d/3vbkBak2MM0u+vhGhlQwpeimUi7QncM=
|
||||
github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=
|
||||
github.com/tjfoc/gmsm v1.0.1 h1:R11HlqhXkDospckjZEihx9SW/2VW0RgdwrykyWMFOQU=
|
||||
github.com/tjfoc/gmsm v1.0.1/go.mod h1:XxO4hdhhrzAd+G4CjDqaOkd0hUzmtPR/d3EiBBMn/wc=
|
||||
github.com/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/fsnotify v1.4.8 h1:zxOoqwE5p91F3rmcYLQheoSeTlIVqwi2b5T7UlpElWE=
|
||||
github.com/wweir/fsnotify v1.4.8/go.mod h1:4GuxIc23DXfJI1/LhBXG5eE/wJ4CzL+kk5M7woxVprc=
|
||||
github.com/wweir/mem-go v0.0.0-20190109100331-8673ab596296 h1:/HkUfg+ZMx/tNdnyJdVlhyv+xO3A7ZlpfL9nFLWLYcc=
|
||||
github.com/wweir/mem-go v0.0.0-20190109100331-8673ab596296/go.mod h1:k7rjBGWoJ+JKwvfe8juAX0zgybjo/Yo3JGkca5f/06s=
|
||||
github.com/wweir/netboot v0.0.2 h1:DFuNd97kxzgblExEKn4Nqf1du0m9qUnaTQ70rFgIjLU=
|
||||
github.com/wweir/netboot v0.0.2/go.mod h1:p/WOnPHdqwgAI6YLEfdmwjwutbc/zDZ7EojAHlXLi4s=
|
||||
github.com/xtaci/kcp-go v5.0.7+incompatible h1:zs9tc8XRID0m+aetu3qPWZFyRt2UIMqbXIBgw+vcnlE=
|
||||
github.com/xtaci/kcp-go v5.0.7+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE=
|
||||
golang.org/x/crypto v0.0.0-20190222235706-ffb98f73852f h1:qWFY9ZxP3tfI37wYIs/MnIAqK0vlXp1xnYEa5HxFSSY=
|
||||
golang.org/x/crypto v0.0.0-20190222235706-ffb98f73852f/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd h1:HuTn7WObtcDo9uEEU7rEqL0jYthdXAmZ6PP+meazmaU=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sys v0.0.0-20190222171317-cd391775e71e h1:oF7qaQxUH6KzFdKN4ww7NpPdo53SZi4UlcksLrb2y/o=
|
||||
golang.org/x/sys v0.0.0-20190222171317-cd391775e71e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
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/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
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/util-go v0.0.0-20200701032032-3cff7b4a46ea h1:V/bIKmS5Bv4zMwQ5qE7ugJiHpcAVFnAxvxRbb7OCwug=
|
||||
github.com/wweir/util-go/log v0.0.0-20200701032032-3cff7b4a46ea h1:IlO7d9R0I/l2syxJkx4/PURW9yVf4hy/Qf2IzdR4y38=
|
||||
github.com/wweir/util-go/log v0.0.0-20200701032032-3cff7b4a46ea/go.mod h1:OObmMboiahxgJ1P3twG+AN8EAZ/wz14vstI4E5kaZzI=
|
||||
github.com/wweir/util-go/mem v0.0.0-20200701032032-3cff7b4a46ea h1:nXgUp7CflDJnuR+zd43DHKxRA/hvuMkFtueSRLY7mEc=
|
||||
github.com/wweir/util-go/mem v0.0.0-20200701032032-3cff7b4a46ea/go.mod h1:gp1gaDO1K4uV/B8NfrTjVim99Xq5xAmm1SHVamRsVW0=
|
||||
go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk=
|
||||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=
|
||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||
go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM=
|
||||
go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
|
||||
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-20200709230013-948cd5f35899 h1:DZhuSZLsGlFL4CmhA8BcRA0mnthyA/nZ00AqCUo7vHg=
|
||||
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-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/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 h1:6fAMxZRR6sl1Uq8U61gxU+kPTs2tR8uOySCbBP7BN/M=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
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/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
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.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
|
||||
@@ -1,28 +1,45 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/golang/glog"
|
||||
"flag"
|
||||
"fmt"
|
||||
|
||||
"github.com/wweir/sower/conf"
|
||||
"github.com/wweir/sower/dns"
|
||||
"github.com/wweir/sower/proxy"
|
||||
"github.com/wweir/sower/router"
|
||||
"github.com/wweir/sower/transport"
|
||||
)
|
||||
|
||||
var version, date string
|
||||
|
||||
func main() {
|
||||
conf := conf.Conf
|
||||
glog.Infof("Starting sower(%s %s): %v", version, date, conf)
|
||||
client, server, password := conf.Init()
|
||||
|
||||
if conf.ServerAddr == "" {
|
||||
proxy.StartServer(conf.NetType, conf.ServerPort, conf.Cipher, conf.Password)
|
||||
switch {
|
||||
case server.Upstream != "":
|
||||
proxy.StartServer(server.Upstream, password, conf.ConfigDir,
|
||||
server.CertFile, server.KeyFile, server.CertEmail)
|
||||
|
||||
} else {
|
||||
if conf.HTTPProxy != "" {
|
||||
go proxy.StartHttpProxy(conf.NetType, conf.ServerAddr,
|
||||
conf.Cipher, conf.Password, conf.HTTPProxy)
|
||||
case client.Address != "":
|
||||
route := router.NewRoute(client.Address, password, client.Router.DetectLevel,
|
||||
client.Router.BlockList, client.Router.ProxyList, client.Router.DirectList,
|
||||
conf.PersistRule)
|
||||
|
||||
if client.Socks5Proxy != "" {
|
||||
go proxy.StartSocks5Proxy(client.Socks5Proxy, client.Address, []byte(password))
|
||||
}
|
||||
|
||||
go dns.StartDNS(conf.DNSServer, conf.ClientIP)
|
||||
proxy.StartClient(conf.NetType, conf.ServerAddr, conf.Cipher, conf.Password, conf.ClientIP)
|
||||
if client.HTTPProxy != "" {
|
||||
go proxy.StartHTTPProxy(client.HTTPProxy, client.Address,
|
||||
[]byte(password), route.GenProxyCheck(true))
|
||||
}
|
||||
|
||||
transport.SetDNS(nil, client.DNSUpstream)
|
||||
go proxy.StartDNS(client.DNSUpstream, route.GenProxyCheck(false))
|
||||
|
||||
proxy.StartClient(client.Address, password,
|
||||
client.PortForward, route.GenProxyCheck(true))
|
||||
|
||||
default:
|
||||
fmt.Println()
|
||||
flag.Usage()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
package parse
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/wweir/sower/util"
|
||||
)
|
||||
|
||||
func ParseAddr(conn net.Conn) (teeConn *util.TeeConn, addr string, err error) {
|
||||
teeConn = &util.TeeConn{Conn: conn}
|
||||
teeConn.StartOrReset()
|
||||
defer teeConn.Stop()
|
||||
|
||||
buf := make([]byte, 1)
|
||||
if n, err := teeConn.Read(buf); err != nil {
|
||||
return teeConn, "", fmt.Errorf("Read conn fail: %v, readed: %v", err, buf[:n])
|
||||
}
|
||||
|
||||
teeConn.StartOrReset()
|
||||
|
||||
// https
|
||||
if buf[0] == 0x16 { // SSL handleshake
|
||||
host, _, err := extractSNI(io.Reader(teeConn))
|
||||
if err != nil {
|
||||
return teeConn, "", err
|
||||
}
|
||||
if strings.Contains(host, ":") {
|
||||
return teeConn, host, nil
|
||||
}
|
||||
return teeConn, host + ":443", nil
|
||||
}
|
||||
|
||||
// http
|
||||
b := bufio.NewReader(teeConn)
|
||||
resp, err := http.ReadRequest(b)
|
||||
if err != nil {
|
||||
return teeConn, "", err
|
||||
}
|
||||
if strings.Contains(resp.Host, ":") {
|
||||
return teeConn, resp.Host, nil
|
||||
}
|
||||
return teeConn, resp.Host + ":80", nil
|
||||
}
|
||||
-232
@@ -1,232 +0,0 @@
|
||||
// Copyright 2016 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package parse
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
func extractSNI(r io.Reader) (string, int, error) {
|
||||
handshake, tlsver, err := handshakeRecord(r)
|
||||
if err != nil {
|
||||
return "", 0, fmt.Errorf("reading TLS record: %s", err)
|
||||
}
|
||||
|
||||
sni, err := parseHello(handshake)
|
||||
if err != nil {
|
||||
return "", 0, fmt.Errorf("reading ClientHello: %s", err)
|
||||
}
|
||||
if len(sni) == 0 {
|
||||
// ClientHello did not present an SNI extension. Valid packet,
|
||||
// no hostname.
|
||||
return "", tlsver, nil
|
||||
}
|
||||
|
||||
hostname, err := parseSNI(sni)
|
||||
if err != nil {
|
||||
return "", 0, fmt.Errorf("parsing SNI extension: %s", err)
|
||||
}
|
||||
return hostname, tlsver, nil
|
||||
}
|
||||
|
||||
// Extract the indicated hostname, if any, from the given SNI
|
||||
// extension bytes.
|
||||
func parseSNI(b []byte) (string, error) {
|
||||
b, _, err := vector(b, 2)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var ret []byte
|
||||
for len(b) >= 3 {
|
||||
typ := b[0]
|
||||
ret, b, err = vector(b[1:], 2)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("truncated SNI extension")
|
||||
}
|
||||
|
||||
if typ == sniHostnameID {
|
||||
return string(ret), nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(b) != 0 {
|
||||
return "", fmt.Errorf("trailing garbage at end of SNI extension")
|
||||
}
|
||||
|
||||
// No DNS-based SNI present.
|
||||
return "", nil
|
||||
}
|
||||
|
||||
const sniExtensionID = 0
|
||||
const sniHostnameID = 0
|
||||
|
||||
// Parse a TLS handshake record as a ClientHello message and extract
|
||||
// the SNI extension bytes, if any.
|
||||
func parseHello(b []byte) ([]byte, error) {
|
||||
if len(b) == 0 {
|
||||
return nil, errors.New("zero length handshake record")
|
||||
}
|
||||
if b[0] != 1 {
|
||||
return nil, fmt.Errorf("non-ClientHello handshake record type %d", b[0])
|
||||
}
|
||||
|
||||
// We're expecting a stricter TLS parser to run after we've
|
||||
// proxied, so we ignore any trailing bytes that might be present
|
||||
// (e.g. another handshake message).
|
||||
b, _, err := vector(b[1:], 3)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading ClientHello: %s", err)
|
||||
}
|
||||
|
||||
// ClientHello must be at least 34 bytes to reach the first vector
|
||||
// length byte. The actual minimal size is larger than that, but
|
||||
// vector() will correctly handle truncated packets.
|
||||
if len(b) < 34 {
|
||||
return nil, errors.New("ClientHello packet too short")
|
||||
}
|
||||
|
||||
if b[0] != 3 {
|
||||
return nil, fmt.Errorf("ClientHello has unsupported version %d.%d", b[0], b[1])
|
||||
}
|
||||
switch b[1] {
|
||||
case 1, 2, 3:
|
||||
// TLS 1.0, TLS 1.1, TLS 1.2
|
||||
default:
|
||||
return nil, fmt.Errorf("TLS record has unsupported version %d.%d", b[0], b[1])
|
||||
}
|
||||
|
||||
// Skip over version and random struct
|
||||
b = b[34:]
|
||||
|
||||
// We don't technically care about SessionID, but we care that the
|
||||
// framing is well-formed all the way up to the SNI field, so that
|
||||
// we are sure that we're pulling the same SNI bytes as the
|
||||
// eventual TLS implementation.
|
||||
vec, b, err := vector(b, 1)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading ClientHello SessionID: %s", err)
|
||||
}
|
||||
if len(vec) > 32 {
|
||||
return nil, fmt.Errorf("ClientHello SessionID too long (%db)", len(vec))
|
||||
}
|
||||
|
||||
// Likewise, we're just checking the bare minimum of framing.
|
||||
vec, b, err = vector(b, 2)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading ClientHello CipherSuites: %s", err)
|
||||
}
|
||||
if len(vec) < 2 || len(vec)%2 != 0 {
|
||||
return nil, fmt.Errorf("ClientHello CipherSuites invalid length %d", len(vec))
|
||||
}
|
||||
|
||||
vec, b, err = vector(b, 1)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading ClientHello CompressionMethods: %s", err)
|
||||
}
|
||||
if len(vec) < 1 {
|
||||
return nil, fmt.Errorf("ClientHello CompressionMethods invalid length %d", len(vec))
|
||||
}
|
||||
|
||||
// Finally, we reach the extensions.
|
||||
if len(b) == 0 {
|
||||
// No extensions. This is not an error, it just means we have
|
||||
// no SNI payload.
|
||||
return nil, nil
|
||||
}
|
||||
b, vec, err = vector(b, 2)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading ClientHello extensions: %s", err)
|
||||
}
|
||||
if len(vec) != 0 {
|
||||
return nil, fmt.Errorf("%d bytes of trailing garbage in ClientHello", len(vec))
|
||||
}
|
||||
|
||||
for len(b) >= 4 {
|
||||
typ := binary.BigEndian.Uint16(b[:2])
|
||||
vec, b, err = vector(b[2:], 2)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading ClientHello extension %d: %s", typ, err)
|
||||
}
|
||||
if typ == sniExtensionID {
|
||||
// Found the SNI extension, return its payload. We don't
|
||||
// care about anything in the packet beyond this point.
|
||||
return vec, nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(b) != 0 {
|
||||
return nil, fmt.Errorf("%d bytes of trailing garbage in ClientHello", len(b))
|
||||
}
|
||||
|
||||
// Successfully parsed all extensions, but there was no SNI.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
const maxTLSRecordLength = 16384
|
||||
|
||||
// Read one TLS record, which must be for the handshake protocol, from r.
|
||||
func handshakeRecord(r io.Reader) ([]byte, int, error) {
|
||||
var hdr struct {
|
||||
Type uint8
|
||||
Major, Minor uint8
|
||||
Length uint16
|
||||
}
|
||||
if err := binary.Read(r, binary.BigEndian, &hdr); err != nil {
|
||||
return nil, 0, fmt.Errorf("reading TLS record header: %s", err)
|
||||
}
|
||||
|
||||
if hdr.Type != 22 {
|
||||
return nil, 0, fmt.Errorf("TLS record is not a handshake")
|
||||
}
|
||||
|
||||
if hdr.Major != 3 {
|
||||
return nil, 0, fmt.Errorf("TLS record has unsupported version %d.%d", hdr.Major, hdr.Minor)
|
||||
}
|
||||
switch hdr.Minor {
|
||||
case 1, 2, 3:
|
||||
// TLS 1.0, TLS 1.1, TLS 1.2
|
||||
default:
|
||||
return nil, 0, fmt.Errorf("TLS record has unsupported version %d.%d", hdr.Major, hdr.Minor)
|
||||
}
|
||||
|
||||
if hdr.Length > maxTLSRecordLength {
|
||||
return nil, 0, fmt.Errorf("TLS record length is greater than %d", maxTLSRecordLength)
|
||||
}
|
||||
|
||||
ret := make([]byte, hdr.Length)
|
||||
if _, err := io.ReadFull(r, ret); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return ret, int(hdr.Minor), nil
|
||||
}
|
||||
|
||||
func vector(b []byte, lenBytes int) ([]byte, []byte, error) {
|
||||
if len(b) < lenBytes {
|
||||
return nil, nil, errors.New("not enough space in packet for vector")
|
||||
}
|
||||
var l int
|
||||
for _, b := range b[:lenBytes] {
|
||||
l = (l << 8) + int(b)
|
||||
}
|
||||
if len(b) < l+lenBytes {
|
||||
return nil, nil, errors.New("not enough space in packet for vector")
|
||||
}
|
||||
return b[lenBytes : l+lenBytes], b[l+lenBytes:], nil
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/wweir/sower/proxy/kcp"
|
||||
"github.com/wweir/sower/proxy/quic"
|
||||
"github.com/wweir/sower/proxy/tcp"
|
||||
"github.com/wweir/sower/shadow"
|
||||
)
|
||||
|
||||
type Client interface {
|
||||
Dial(server string) (net.Conn, error)
|
||||
}
|
||||
|
||||
func NewClient(netType string) Client {
|
||||
switch netType {
|
||||
case QUIC.String():
|
||||
return quic.NewClient()
|
||||
case KCP.String():
|
||||
return kcp.NewClient()
|
||||
case TCP.String():
|
||||
return tcp.NewClient()
|
||||
default:
|
||||
glog.Fatalln("invalid net type: " + netType)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func StartClient(netType, server, cipher, password, listenIP string) {
|
||||
connCh := listenLocal(listenIP, []string{"80", "443"})
|
||||
client := NewClient(netType)
|
||||
resolved := false
|
||||
|
||||
glog.Infoln("Client started.")
|
||||
for {
|
||||
conn := <-connCh
|
||||
glog.V(1).Infof("new conn from (%s) to (%s)", conn.RemoteAddr(), server)
|
||||
|
||||
if !resolved {
|
||||
if addr, err := net.ResolveTCPAddr("tcp", server); err != nil {
|
||||
glog.Errorln(err)
|
||||
} else {
|
||||
server = addr.String()
|
||||
resolved = true
|
||||
}
|
||||
}
|
||||
|
||||
rc, err := client.Dial(server)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
glog.Errorln(err)
|
||||
continue
|
||||
}
|
||||
rc = shadow.Shadow(rc, cipher, password)
|
||||
|
||||
go relay(conn, rc)
|
||||
}
|
||||
}
|
||||
|
||||
func listenLocal(listenIP string, ports []string) <-chan net.Conn {
|
||||
connCh := make(chan net.Conn, 10)
|
||||
for i := range ports {
|
||||
go func(port string) {
|
||||
ln, err := net.Listen("tcp", net.JoinHostPort(listenIP, port))
|
||||
if err != nil {
|
||||
glog.Fatalln(err)
|
||||
}
|
||||
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
glog.Errorln("accept", listenIP+port, "fail:", err)
|
||||
continue
|
||||
}
|
||||
|
||||
conn.(*net.TCPConn).SetKeepAlive(true)
|
||||
connCh <- conn
|
||||
}
|
||||
}(ports[i])
|
||||
}
|
||||
|
||||
glog.Infoln("listening ports:", ports)
|
||||
return connCh
|
||||
}
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/wweir/sower/dhcp"
|
||||
"github.com/wweir/util-go/log"
|
||||
)
|
||||
|
||||
type msgCache struct {
|
||||
*dns.Msg
|
||||
time.Time
|
||||
}
|
||||
|
||||
var cache sync.Map
|
||||
|
||||
func StartDNS(relayServer string, shouldProxy func(string) (bool, bool)) {
|
||||
var err error
|
||||
if relayServer, err = pickRelayAddr(relayServer); err != nil {
|
||||
log.Fatalw("pick upstream dns server", "err", err)
|
||||
}
|
||||
log.Infow("upstream dns", "addr", relayServer)
|
||||
|
||||
dns.HandleFunc(".", func(w dns.ResponseWriter, r *dns.Msg) {
|
||||
// *Msg r has an TSIG record and it was validated
|
||||
if r.IsTsig() != nil && w.TsigStatus() == nil {
|
||||
lastTsig := r.Extra[len(r.Extra)-1].(*dns.TSIG)
|
||||
r.SetTsig(lastTsig.Hdr.Name, dns.HmacMD5, 300, time.Now().Unix())
|
||||
}
|
||||
|
||||
//https://stackoverflow.com/questions/4082081/requesting-a-and-aaaa-records-in-single-dns-query/4083071#4083071
|
||||
if len(r.Question) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
domain := r.Question[0].Name
|
||||
if idx := strings.IndexByte(domain, ':'); idx > 0 {
|
||||
domain = domain[:idx] // trim port
|
||||
}
|
||||
|
||||
if isblock, isproxy := shouldProxy(domain); isblock {
|
||||
m := new(dns.Msg)
|
||||
m.SetReply(r)
|
||||
w.WriteMsg(m)
|
||||
} else if isproxy {
|
||||
host, _, _ := net.SplitHostPort(w.LocalAddr().String())
|
||||
w.WriteMsg(localA(r, domain, net.ParseIP(host)))
|
||||
|
||||
} else if val, ok := cache.Load(domain); ok && val.(*msgCache).After(time.Now()) {
|
||||
msg := val.(*msgCache)
|
||||
for _, rr := range msg.Answer {
|
||||
rr.Header().Ttl = uint32(time.Until(msg.Time).Seconds())
|
||||
}
|
||||
msg.SetReply(r)
|
||||
w.WriteMsg(msg.Msg)
|
||||
|
||||
} else if msg, err := dns.Exchange(r, relayServer); err != nil || msg == nil {
|
||||
cache.Delete(domain)
|
||||
if server, err := pickRelayAddr(relayServer); err != nil {
|
||||
log.Errorw("detect upstream dns", "err", err)
|
||||
} else if relayServer != server {
|
||||
relayServer = server
|
||||
log.Infow("detect upstream dns", "addr", relayServer)
|
||||
}
|
||||
|
||||
} else {
|
||||
cache.Delete(domain)
|
||||
if len(msg.Answer) != 0 {
|
||||
deadline := time.Now().Add(
|
||||
time.Duration(msg.Answer[0].Header().Ttl) * time.Second)
|
||||
cache.Store(domain, &msgCache{
|
||||
Msg: msg,
|
||||
Time: deadline,
|
||||
})
|
||||
}
|
||||
|
||||
w.WriteMsg(msg)
|
||||
}
|
||||
})
|
||||
|
||||
server := &dns.Server{Addr: ":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
|
||||
}
|
||||
+45
-64
@@ -3,35 +3,42 @@ package proxy
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/wweir/sower/shadow"
|
||||
"github.com/wweir/sower/transport"
|
||||
"github.com/wweir/sower/util"
|
||||
"github.com/wweir/util-go/log"
|
||||
)
|
||||
|
||||
func StartHttpProxy(netType, server, cipher, password, addr string) {
|
||||
client := NewClient(netType)
|
||||
resolved := false
|
||||
// StartHTTPProxy start http reverse proxy.
|
||||
// The httputil.ReverseProxy do not supply enough support for https request.
|
||||
func StartHTTPProxy(httpProxyAddr, serverAddr string, password []byte,
|
||||
shouldProxy func(string) (bool, bool)) {
|
||||
|
||||
proxy := httputil.ReverseProxy{
|
||||
Director: func(r *http.Request) {},
|
||||
Transport: &http.Transport{
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
return transport.Dial(serverAddr, func(host string) (string, []byte) {
|
||||
if _, ok := shouldProxy(host); ok {
|
||||
return httpProxyAddr, password
|
||||
}
|
||||
return "", nil
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: addr,
|
||||
Addr: httpProxyAddr,
|
||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if !resolved {
|
||||
if addr, err := net.ResolveTCPAddr("tcp", server); err != nil {
|
||||
glog.Errorln(err)
|
||||
} else {
|
||||
server = addr.String()
|
||||
resolved = true
|
||||
}
|
||||
}
|
||||
|
||||
if r.Method == http.MethodConnect {
|
||||
httpsProxy(w, r, client, server, cipher, password)
|
||||
httpsProxy(w, r, serverAddr, password, shouldProxy)
|
||||
} else {
|
||||
httpProxy(w, r, client, server, cipher, password)
|
||||
proxy.ServeHTTP(w, r)
|
||||
}
|
||||
}),
|
||||
// Disable HTTP/2.
|
||||
@@ -39,65 +46,39 @@ func StartHttpProxy(netType, server, cipher, password, addr string) {
|
||||
IdleTimeout: 90 * time.Second,
|
||||
}
|
||||
|
||||
glog.Fatalln(srv.ListenAndServe())
|
||||
log.Infow("start sower http proxy", "http_proxy", httpProxyAddr)
|
||||
go log.Fatalw("serve http proxy", "addr", httpProxyAddr, "err", srv.ListenAndServe())
|
||||
}
|
||||
|
||||
func httpProxy(w http.ResponseWriter, req *http.Request, client Client, server, cipher, password string) {
|
||||
roundTripper := &http.Transport{
|
||||
DialContext: func(context.Context, string, string) (net.Conn, error) {
|
||||
conn, err := client.Dial(server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return shadow.Shadow(conn, cipher, password), nil
|
||||
},
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
}
|
||||
func httpsProxy(w http.ResponseWriter, r *http.Request,
|
||||
serverAddr string, password []byte, shouldProxy func(string) (bool, bool)) {
|
||||
|
||||
resp, err := roundTripper.RoundTrip(req)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusServiceUnavailable)
|
||||
glog.Errorln("serve https proxy, get remote data:", err)
|
||||
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, client Client, server, cipher, password string) {
|
||||
// local conn
|
||||
conn, _, err := w.(http.Hijacker).Hijack()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
conn.(*net.TCPConn).SetKeepAlive(true)
|
||||
defer conn.Close()
|
||||
|
||||
if _, err := conn.Write([]byte(r.Proto + " 200 Connection established\r\n\r\n")); err != nil {
|
||||
conn.Close()
|
||||
glog.Errorln("serve https proxy, write data fail:", err)
|
||||
return
|
||||
}
|
||||
|
||||
// remote conn
|
||||
rc, err := client.Dial(server)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
http.Error(w, err.Error(), http.StatusServiceUnavailable)
|
||||
glog.Errorln("serve https proxy, dial remote fail:", err)
|
||||
return
|
||||
}
|
||||
rc = shadow.Shadow(rc, cipher, password)
|
||||
|
||||
relay(rc, conn)
|
||||
target, _ := util.WithDefaultPort(r.Host, "443")
|
||||
rc, err := transport.Dial(target, func(host string) (string, []byte) {
|
||||
if _, ok := shouldProxy(host); ok {
|
||||
return serverAddr, password
|
||||
}
|
||||
return "", nil
|
||||
})
|
||||
if err != nil {
|
||||
conn.Write([]byte("sower dial " + serverAddr + " fail: " + err.Error()))
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
relay(conn, rc)
|
||||
}
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
package kcp
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
kcp "github.com/xtaci/kcp-go"
|
||||
)
|
||||
|
||||
type client struct {
|
||||
DataShard int
|
||||
ParityShard int
|
||||
DSCP int
|
||||
SockBuf int
|
||||
AckNodelay bool
|
||||
NoDelay int
|
||||
Interval int
|
||||
Resend int
|
||||
NoCongestion int
|
||||
SndWnd int
|
||||
RcvWnd int
|
||||
MTU int
|
||||
}
|
||||
|
||||
func NewClient() *client {
|
||||
return &client{
|
||||
DataShard: 10,
|
||||
ParityShard: 3,
|
||||
DSCP: 0,
|
||||
SockBuf: 4194304,
|
||||
NoDelay: 0,
|
||||
Interval: 50,
|
||||
Resend: 0,
|
||||
NoCongestion: 0,
|
||||
SndWnd: 0,
|
||||
RcvWnd: 0,
|
||||
MTU: 1350,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *client) Dial(server string) (net.Conn, error) {
|
||||
conn, err := kcp.DialWithOptions(server, nil, c.DataShard, c.ParityShard)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "dial")
|
||||
}
|
||||
|
||||
conn.SetStreamMode(true)
|
||||
conn.SetWriteDelay(false)
|
||||
conn.SetNoDelay(c.NoDelay, c.Interval, c.Resend, c.NoCongestion)
|
||||
conn.SetWindowSize(c.SndWnd, c.RcvWnd)
|
||||
conn.SetMtu(c.MTU)
|
||||
conn.SetACKNoDelay(c.AckNodelay)
|
||||
|
||||
if err := conn.SetDSCP(c.DSCP); err != nil {
|
||||
return nil, errors.Wrap(err, "SetDSCP")
|
||||
}
|
||||
if err := conn.SetReadBuffer(c.SockBuf); err != nil {
|
||||
return nil, errors.Wrap(err, "SetReadBuffer")
|
||||
}
|
||||
if err := conn.SetWriteBuffer(c.SockBuf); err != nil {
|
||||
return nil, errors.Wrap(err, "SetWriteBuffer")
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
package kcp
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/pkg/errors"
|
||||
kcp "github.com/xtaci/kcp-go"
|
||||
)
|
||||
|
||||
type server struct {
|
||||
DataShard int
|
||||
ParityShard int
|
||||
DSCP int
|
||||
SockBuf int
|
||||
}
|
||||
|
||||
func NewServer() *server {
|
||||
return &server{
|
||||
DataShard: 10,
|
||||
ParityShard: 3,
|
||||
DSCP: 0,
|
||||
SockBuf: 4194304,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) Listen(port string) (<-chan net.Conn, error) {
|
||||
ln, err := kcp.ListenWithOptions(port, nil, s.DataShard, s.ParityShard)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := ln.SetDSCP(s.DSCP); err != nil {
|
||||
return nil, errors.Wrap(err, "SetDSCP")
|
||||
}
|
||||
if err := ln.SetReadBuffer(s.SockBuf); err != nil {
|
||||
return nil, errors.Wrap(err, "SetReadBuffer")
|
||||
}
|
||||
if err := ln.SetWriteBuffer(s.SockBuf); err != nil {
|
||||
return nil, errors.Wrap(err, "SetWriteBuffer")
|
||||
}
|
||||
|
||||
connCh := make(chan net.Conn)
|
||||
go func() {
|
||||
for {
|
||||
conn, err := ln.AcceptKCP()
|
||||
if err != nil {
|
||||
glog.Fatalln("KCP listen:", err)
|
||||
}
|
||||
|
||||
connCh <- conn
|
||||
}
|
||||
}()
|
||||
|
||||
return connCh, nil
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
// Code generated by "stringer -type=netType util.go"; DO NOT EDIT.
|
||||
|
||||
package proxy
|
||||
|
||||
import "strconv"
|
||||
|
||||
const _netType_name = "QUICKCPTCP"
|
||||
|
||||
var _netType_index = [...]uint8{0, 4, 7, 10}
|
||||
|
||||
func (i netType) String() string {
|
||||
if i < 0 || i >= netType(len(_netType_index)-1) {
|
||||
return "netType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _netType_name[_netType_index[i]:_netType_index[i+1]]
|
||||
}
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/wweir/sower/transport"
|
||||
"github.com/wweir/util-go/log"
|
||||
"golang.org/x/crypto/acme/autocert"
|
||||
)
|
||||
|
||||
func StartClient(serverAddr, password string,
|
||||
forwards map[string]string, shouldProxy func(string) (bool, bool)) {
|
||||
|
||||
passwordData := []byte(password)
|
||||
relayToRemote := func(lnAddr, target string,
|
||||
parseFn func(net.Conn) (net.Conn, string, error),
|
||||
shouldProxy func(string) (bool, bool)) {
|
||||
|
||||
ln, err := net.Listen("tcp", lnAddr)
|
||||
if err != nil {
|
||||
log.Fatalw("tcp listen", "port", lnAddr, "err", err)
|
||||
}
|
||||
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
log.Errorw("tcp accept", "port", lnAddr, "err", err)
|
||||
continue
|
||||
}
|
||||
|
||||
go func(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
|
||||
if parseFn != nil {
|
||||
if conn, target, err = parseFn(conn); err != nil {
|
||||
log.Warnw("parse target", "err", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
rc, err := transport.Dial(target, func(domain string) (string, []byte) {
|
||||
if _, ok := shouldProxy(domain); ok {
|
||||
return serverAddr, passwordData
|
||||
}
|
||||
return "", nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Warnw("dial", "addr", target, "err", err)
|
||||
return
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
relay(conn, rc)
|
||||
}(conn)
|
||||
}
|
||||
}
|
||||
|
||||
for from, to := range forwards {
|
||||
go relayToRemote(from, to, nil, func(string) (bool, bool) { return false, true })
|
||||
}
|
||||
|
||||
go relayToRemote(":http", "", ParseHTTP, shouldProxy)
|
||||
go relayToRemote(":https", "", ParseHTTPS, shouldProxy)
|
||||
|
||||
log.Infow("start sower client", "forwards", forwards)
|
||||
|
||||
select {}
|
||||
}
|
||||
|
||||
func StartServer(relayTarget, password, cacheDir, certFile, keyFile, email string) {
|
||||
certManager := autocert.Manager{
|
||||
Prompt: autocert.AcceptTOS,
|
||||
Email: email,
|
||||
Cache: autocert.DirCache(cacheDir),
|
||||
}
|
||||
|
||||
tlsConf := &tls.Config{
|
||||
GetCertificate: certManager.GetCertificate,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
NextProtos: []string{"http/1.1", "h2"},
|
||||
}
|
||||
if certFile != "" && keyFile != "" {
|
||||
if cert, err := tls.LoadX509KeyPair(certFile, keyFile); err != nil {
|
||||
log.Fatalw("load certificate", "cert", certFile, "key", keyFile, "err", err)
|
||||
} else {
|
||||
tlsConf.GetCertificate = nil
|
||||
tlsConf.Certificates = []tls.Certificate{cert}
|
||||
}
|
||||
}
|
||||
|
||||
// Try to redirect 80 to 443
|
||||
go http.ListenAndServe(":80", certManager.HTTPHandler(http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
r.URL.Scheme = "https"
|
||||
if host, _, err := net.SplitHostPort(r.Host); err != nil {
|
||||
r.URL.Host = r.Host
|
||||
} else {
|
||||
r.URL.Host = host
|
||||
}
|
||||
|
||||
http.Redirect(w, r, r.URL.String(), 301)
|
||||
})))
|
||||
|
||||
log.Infow("start sower server", "relay_to", relayTarget)
|
||||
ln, err := tls.Listen("tcp", ":443", tlsConf)
|
||||
if err != nil {
|
||||
log.Fatalw("tcp listen", "err", err)
|
||||
}
|
||||
|
||||
passwordData := []byte(password)
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
log.Errorw("tcp accept", "err", err)
|
||||
continue
|
||||
}
|
||||
|
||||
go func(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
|
||||
target := relayTarget
|
||||
|
||||
conn, t := transport.ParseTrojanConn(conn, passwordData)
|
||||
if t != nil {
|
||||
target = t.Addr()
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package quic
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
quic "github.com/lucas-clemente/quic-go"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/wweir/sower/util"
|
||||
)
|
||||
|
||||
type client struct {
|
||||
conf *quic.Config
|
||||
sess quic.Session
|
||||
}
|
||||
|
||||
func NewClient() *client {
|
||||
return &client{
|
||||
conf: &quic.Config{
|
||||
HandshakeTimeout: 5 * time.Second,
|
||||
MaxIncomingStreams: 1024,
|
||||
KeepAlive: true,
|
||||
IdleTimeout: 5 * time.Minute,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *client) Dial(server string) (net.Conn, error) {
|
||||
if c.sess == nil {
|
||||
if sess, err := quic.DialAddr(server, &tls.Config{InsecureSkipVerify: true}, c.conf); err != nil {
|
||||
return nil, errors.Wrap(err, "session")
|
||||
} else {
|
||||
go func() {
|
||||
<-sess.Context().Done()
|
||||
sess.Close()
|
||||
c.sess = nil
|
||||
}()
|
||||
c.sess = sess
|
||||
}
|
||||
}
|
||||
|
||||
var stream quic.Stream
|
||||
if err := util.WithTimeout(func() (err error) {
|
||||
if stream, err = c.sess.OpenStream(); err != nil {
|
||||
c.sess = nil
|
||||
}
|
||||
return
|
||||
}, time.Second); err != nil {
|
||||
return nil, errors.Wrap(err, "stream")
|
||||
}
|
||||
|
||||
return &streamConn{
|
||||
Stream: stream,
|
||||
sess: c.sess,
|
||||
}, nil
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
package quic
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
quic "github.com/lucas-clemente/quic-go"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type server struct {
|
||||
conf *quic.Config
|
||||
}
|
||||
|
||||
func NewServer() *server {
|
||||
return &server{
|
||||
conf: &quic.Config{
|
||||
HandshakeTimeout: 5 * time.Second,
|
||||
MaxIncomingStreams: 1024,
|
||||
KeepAlive: true,
|
||||
IdleTimeout: 5 * time.Minute,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) Listen(port string) (<-chan net.Conn, error) {
|
||||
ln, err := quic.ListenAddr(port, mockTlsPem(), s.conf)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
connCh := make(chan net.Conn)
|
||||
go func() {
|
||||
for {
|
||||
sess, err := ln.Accept()
|
||||
if err != nil {
|
||||
glog.Fatalln(err)
|
||||
}
|
||||
go accept(sess, connCh)
|
||||
}
|
||||
}()
|
||||
return connCh, nil
|
||||
}
|
||||
|
||||
func accept(sess quic.Session, connCh chan<- net.Conn) {
|
||||
glog.V(1).Infoln("new session from ", sess.RemoteAddr())
|
||||
defer sess.Close()
|
||||
|
||||
for {
|
||||
stream, err := sess.AcceptStream()
|
||||
if err != nil {
|
||||
glog.Errorln(err)
|
||||
return
|
||||
}
|
||||
|
||||
connCh <- &streamConn{stream, sess}
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package quic
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"math/big"
|
||||
"net"
|
||||
|
||||
"github.com/golang/glog"
|
||||
quic "github.com/lucas-clemente/quic-go"
|
||||
)
|
||||
|
||||
type streamConn struct {
|
||||
quic.Stream
|
||||
sess quic.Session
|
||||
}
|
||||
|
||||
func (s *streamConn) LocalAddr() net.Addr {
|
||||
return s.sess.LocalAddr()
|
||||
}
|
||||
|
||||
func (s *streamConn) RemoteAddr() net.Addr {
|
||||
return s.sess.RemoteAddr()
|
||||
}
|
||||
|
||||
func mockTlsPem() *tls.Config {
|
||||
key, err := rsa.GenerateKey(rand.Reader, 1024)
|
||||
if err != nil {
|
||||
glog.Fatalln(err)
|
||||
}
|
||||
template := x509.Certificate{SerialNumber: big.NewInt(1)}
|
||||
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
|
||||
if err != nil {
|
||||
glog.Fatalln(err)
|
||||
}
|
||||
|
||||
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
|
||||
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
|
||||
|
||||
tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
|
||||
if err != nil {
|
||||
glog.Fatalln(err)
|
||||
}
|
||||
return &tls.Config{Certificates: []tls.Certificate{tlsCert}}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/wweir/sower/parse"
|
||||
"github.com/wweir/sower/proxy/kcp"
|
||||
"github.com/wweir/sower/proxy/quic"
|
||||
"github.com/wweir/sower/proxy/tcp"
|
||||
"github.com/wweir/sower/shadow"
|
||||
)
|
||||
|
||||
type Server interface {
|
||||
Listen(port string) (<-chan net.Conn, error)
|
||||
}
|
||||
|
||||
func NewServer(netType string) Server {
|
||||
switch netType {
|
||||
case QUIC.String():
|
||||
return quic.NewServer()
|
||||
case KCP.String():
|
||||
return kcp.NewServer()
|
||||
case TCP.String():
|
||||
return tcp.NewServer()
|
||||
default:
|
||||
glog.Fatalln("invalid net type: " + netType)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func StartServer(netType, port, cipher, password string) {
|
||||
if port == "" {
|
||||
glog.Fatalln("port must set")
|
||||
}
|
||||
if !strings.Contains(port, ":") {
|
||||
port = ":" + port
|
||||
}
|
||||
|
||||
connCh, err := NewServer(netType).Listen(port)
|
||||
if err != nil {
|
||||
glog.Fatalf("listen %v fail: %s", port, err)
|
||||
}
|
||||
|
||||
glog.Infoln("Server started.")
|
||||
for {
|
||||
conn := <-connCh
|
||||
conn = shadow.Shadow(conn, cipher, password)
|
||||
|
||||
go handle(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func handle(conn net.Conn) {
|
||||
conn, addr, err := parse.ParseAddr(conn)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
glog.Warningln(err)
|
||||
return
|
||||
}
|
||||
glog.V(1).Infof("new conn from %s to %s", conn.RemoteAddr(), addr)
|
||||
|
||||
rc, err := net.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
glog.Warningln(err)
|
||||
return
|
||||
}
|
||||
rc.(*net.TCPConn).SetKeepAlive(true)
|
||||
|
||||
relay(rc, conn)
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/wweir/sower/transport"
|
||||
"github.com/wweir/util-go/log"
|
||||
)
|
||||
|
||||
func StartSocks5Proxy(listenAddr, serverAddr string, password []byte) {
|
||||
ln, err := net.Listen("tcp", listenAddr)
|
||||
if err != nil {
|
||||
log.Fatalw("socks5 proxy", "addr", listenAddr, "err", err)
|
||||
}
|
||||
|
||||
log.Infow("start socks5 proxy", "endpoint", listenAddr)
|
||||
serveSocks5(ln, serverAddr, password)
|
||||
}
|
||||
|
||||
func serveSocks5(ln net.Listener, serverAddr string, password []byte) {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
log.Fatalw("socks5 proxy", "err", err)
|
||||
}
|
||||
go serveSocks5(ln, serverAddr, password)
|
||||
|
||||
tgtAddr, err := transport.ParseSocks5(conn)
|
||||
if err != nil {
|
||||
log.Errorw("socks5 proxy", "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
rc, err := transport.Dial(tgtAddr, func(host string) (string, []byte) {
|
||||
return serverAddr, password
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorw("socks5 proxy", "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
relay(conn, rc)
|
||||
conn.Close()
|
||||
rc.Close()
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
type client struct {
|
||||
DialTimeout time.Duration
|
||||
}
|
||||
|
||||
func NewClient() *client {
|
||||
return &client{
|
||||
DialTimeout: 5 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *client) Dial(server string) (net.Conn, error) {
|
||||
conn, err := net.DialTimeout("tcp", server, c.DialTimeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conn.(*net.TCPConn).SetKeepAlive(true)
|
||||
return conn, nil
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
type server struct {
|
||||
}
|
||||
|
||||
func NewServer() *server {
|
||||
return &server{}
|
||||
}
|
||||
|
||||
func (s *server) Listen(port string) (<-chan net.Conn, error) {
|
||||
ln, err := net.Listen("tcp", port)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
connCh := make(chan net.Conn)
|
||||
go func() {
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
glog.Fatalln("TCP listen:", err)
|
||||
}
|
||||
|
||||
conn.(*net.TCPConn).SetKeepAlive(true)
|
||||
connCh <- conn
|
||||
}
|
||||
}()
|
||||
return connCh, nil
|
||||
}
|
||||
+32
-15
@@ -1,23 +1,46 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/wweir/sower/util"
|
||||
)
|
||||
|
||||
//go:generate stringer -type=netType $GOFILE
|
||||
type netType int
|
||||
func ParseHTTP(conn net.Conn) (net.Conn, string, error) {
|
||||
teeConn := &util.TeeConn{Conn: conn}
|
||||
defer teeConn.Stop()
|
||||
|
||||
const (
|
||||
QUIC netType = iota
|
||||
KCP
|
||||
TCP
|
||||
)
|
||||
resp, err := http.ReadRequest(bufio.NewReader(teeConn))
|
||||
if err != nil {
|
||||
return teeConn, "", err
|
||||
}
|
||||
|
||||
resp.Host, _ = util.WithDefaultPort(resp.Host, "80")
|
||||
return teeConn, resp.Host, nil
|
||||
}
|
||||
|
||||
func ParseHTTPS(conn net.Conn) (net.Conn, string, error) {
|
||||
teeConn := &util.TeeConn{Conn: conn}
|
||||
defer teeConn.Stop()
|
||||
|
||||
var domain string
|
||||
tls.Server(teeConn, &tls.Config{
|
||||
GetConfigForClient: func(hello *tls.ClientHelloInfo) (*tls.Config, error) {
|
||||
domain = hello.ServerName
|
||||
return nil, nil
|
||||
},
|
||||
}).Handshake()
|
||||
|
||||
domain, _ = util.WithDefaultPort(domain, "443")
|
||||
return teeConn, domain, nil
|
||||
}
|
||||
|
||||
func relay(conn1, conn2 net.Conn) {
|
||||
wg := &sync.WaitGroup{}
|
||||
@@ -27,20 +50,14 @@ func relay(conn1, conn2 net.Conn) {
|
||||
redirect(conn1, conn2, wg, exitFlag)
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func redirect(dst, src net.Conn, wg *sync.WaitGroup, exitFlag *int32) {
|
||||
if _, err := io.Copy(dst, src); err != nil {
|
||||
glog.V(1).Infof("%s<>%s -> %s<>%s: %s", src.RemoteAddr(), src.LocalAddr(), dst.LocalAddr(), dst.RemoteAddr(), err)
|
||||
}
|
||||
io.Copy(dst, src)
|
||||
|
||||
if atomic.CompareAndSwapInt32(exitFlag, 0, 1) {
|
||||
// wakeup blocked goroutine
|
||||
now := time.Now()
|
||||
src.SetDeadline(now)
|
||||
dst.SetDeadline(now)
|
||||
} else {
|
||||
src.Close()
|
||||
dst.Close()
|
||||
}
|
||||
|
||||
wg.Done()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package dns
|
||||
package router
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -6,56 +6,49 @@ import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// HTTPPing try connect to a http(s) server with domain though the http addr
|
||||
func HTTPPing(viaHost, domain string, port Port, timeout time.Duration) (err error) {
|
||||
conn, err := net.DialTimeout("tcp", port.JoinAddr(viaHost), timeout)
|
||||
// Port ==========================
|
||||
type Port uint16
|
||||
|
||||
const (
|
||||
HTTP Port = 80
|
||||
HTTPS Port = 443
|
||||
)
|
||||
|
||||
// Ping try connect to a http(s) server with domain though the http addr
|
||||
func (p Port) Ping(domain string, dial func(string) (net.Conn, error)) error {
|
||||
conn, err := dial(net.JoinHostPort(domain, p.String()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
conn.SetDeadline(time.Now().Add(timeout))
|
||||
if _, err = conn.Write(port.pingMsg(domain)); err != nil {
|
||||
conn.SetDeadline(time.Now().Add(5 * time.Second))
|
||||
if _, err := conn.Write(p.PingMsg(domain)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// err -> nil: read something succ
|
||||
// err -> io.EOF: no such domain or connection refused
|
||||
// err -> timeout: tcp package has been dropped
|
||||
_, err = conn.Read(make([]byte, 1))
|
||||
if err == io.EOF && viaHost == domain {
|
||||
return nil
|
||||
if _, err = conn.Read(make([]byte, 10)); err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Port ==========================
|
||||
type Port uint16
|
||||
|
||||
const (
|
||||
Http Port = iota
|
||||
Https
|
||||
)
|
||||
|
||||
func (p Port) JoinAddr(addr string) string {
|
||||
switch p {
|
||||
case Http:
|
||||
return addr + ":80"
|
||||
case Https:
|
||||
return addr + ":443"
|
||||
default:
|
||||
panic("invalid port")
|
||||
}
|
||||
func (p Port) String() string {
|
||||
return strconv.Itoa(int(p))
|
||||
}
|
||||
|
||||
func (p Port) pingMsg(domain string) []byte {
|
||||
func (p Port) PingMsg(domain string) []byte {
|
||||
switch p {
|
||||
case Http:
|
||||
case HTTP:
|
||||
return []byte("TRACE / HTTP/1.1\r\nHost: " + domain + "\r\n\r\n")
|
||||
case Https:
|
||||
case HTTPS:
|
||||
return NewClientHelloSNIMsg(domain)
|
||||
default:
|
||||
panic("invalid port")
|
||||
@@ -99,7 +92,7 @@ func NewClientHelloSNIMsg(domain string) []byte {
|
||||
length := uint16(len(domain))
|
||||
msg := &clientHelloSNI{
|
||||
ContentType: 0x16, // Content Type: Handshake (22)
|
||||
Version: 0x0301, // Version: TLS 1.0 (0x0301)
|
||||
Version: 0x0301, // Version: TLS 1.0
|
||||
Length: length + 56,
|
||||
handshakeProtocol: handshakeProtocol{
|
||||
HandshakeType: 0x01, // Handshake Type: Client Hello (1)
|
||||
@@ -0,0 +1,72 @@
|
||||
// +build debug
|
||||
// go test -v -tags debug
|
||||
|
||||
package router_test
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/wweir/sower/router"
|
||||
"github.com/wweir/sower/transport"
|
||||
)
|
||||
|
||||
var (
|
||||
proxyAddr string = "socks5://127.0.0.1:1080"
|
||||
password []byte = nil
|
||||
)
|
||||
|
||||
func TestPort_Ping(t *testing.T) {
|
||||
direct := func(addr string) (net.Conn, error) {
|
||||
return net.DialTimeout("tcp", addr, 5*time.Second)
|
||||
}
|
||||
proxy := func(addr string) (net.Conn, error) {
|
||||
conn, err := tls.Dial("tcp", proxyAddr, &tls.Config{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
host, port, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p, err := strconv.Atoi(port)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return transport.ToTrojanConn(conn, host, uint16(p), password)
|
||||
}
|
||||
|
||||
type args struct {
|
||||
domain string
|
||||
dial func(string) (net.Conn, error)
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
p router.Port
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{"", router.HTTP, args{"baidu.com", direct}, false},
|
||||
{"", router.HTTP, args{"baidu.com", proxy}, false},
|
||||
{"", router.HTTPS, args{"baidu.com", direct}, false},
|
||||
{"", router.HTTPS, args{"baidu.com", proxy}, false},
|
||||
{"", router.HTTP, args{"google.com", direct}, true},
|
||||
{"", router.HTTP, args{"google.com", proxy}, false},
|
||||
{"", router.HTTPS, args{"google.com", direct}, true},
|
||||
{"", router.HTTPS, args{"google.com", proxy}, false},
|
||||
{"", router.HTTP, args{"smtp.163.com", direct}, true},
|
||||
{"", router.HTTP, args{"smtp.163.com", proxy}, true},
|
||||
{"", router.HTTPS, args{"smtp.163.com", direct}, true},
|
||||
{"", router.HTTPS, args{"smtp.163.com", proxy}, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := tt.p.Ping(tt.args.domain, tt.args.dial); (err != nil) != tt.wantErr {
|
||||
t.Errorf("Port.Ping() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/wweir/sower/transport"
|
||||
"github.com/wweir/sower/util"
|
||||
"github.com/wweir/util-go/log"
|
||||
"github.com/wweir/util-go/mem"
|
||||
)
|
||||
|
||||
// Route implement a router for each request
|
||||
type Route struct {
|
||||
once sync.Once
|
||||
cache *mem.Cache
|
||||
|
||||
ProxyAddress string
|
||||
password []byte
|
||||
|
||||
DetectLevel int // dynamic detect proxy level
|
||||
blockRule *util.Node
|
||||
proxyRule *util.Node
|
||||
directRule *util.Node
|
||||
PersistFn func(string)
|
||||
}
|
||||
|
||||
func NewRoute(address, password string, detectLevel int,
|
||||
blocklist, proxylist, directlist []string, persist func(string)) *Route {
|
||||
return &Route{
|
||||
cache: mem.New(4 * time.Hour),
|
||||
ProxyAddress: address,
|
||||
password: []byte(password),
|
||||
|
||||
DetectLevel: detectLevel,
|
||||
blockRule: util.NewNodeFromRules(blocklist...),
|
||||
proxyRule: util.NewNodeFromRules(proxylist...),
|
||||
directRule: util.NewNodeFromRules(directlist...),
|
||||
PersistFn: persist,
|
||||
}
|
||||
}
|
||||
|
||||
// ShouldProxy check if the domain shoule request though proxy
|
||||
func (r *Route) GenProxyCheck(sync bool) func(string) (bool, bool) {
|
||||
detect := func(domain string) bool {
|
||||
go r.cache.Remember(r, domain)
|
||||
return true
|
||||
}
|
||||
if sync {
|
||||
detect = func(domain string) bool {
|
||||
r.cache.Remember(r, domain)
|
||||
if r.proxyRule.Match(domain) {
|
||||
return true
|
||||
}
|
||||
return !r.directRule.Match(domain)
|
||||
}
|
||||
}
|
||||
|
||||
return func(domain string) (bool, bool) {
|
||||
domain = strings.TrimSuffix(domain, ".")
|
||||
// break deadlook, for wildcard
|
||||
if sepCount := strings.Count(domain, "."); sepCount == 0 || sepCount >= 5 {
|
||||
return false, false
|
||||
}
|
||||
|
||||
if r.blockRule.Match(domain) {
|
||||
return true, false
|
||||
}
|
||||
|
||||
if r.proxyRule.Match(domain) {
|
||||
return false, true
|
||||
}
|
||||
if r.directRule.Match(domain) {
|
||||
return false, false
|
||||
}
|
||||
|
||||
return false, detect(domain)
|
||||
}
|
||||
}
|
||||
|
||||
// Get implement for cache
|
||||
func (r *Route) Get(key interface{}) (err error) {
|
||||
domain := key.(string)
|
||||
|
||||
httpScore, httpsScore := r.detect(domain)
|
||||
log.Infow("detect", "domain", domain, "http", httpScore, "https", httpsScore)
|
||||
|
||||
if httpScore+httpsScore >= r.DetectLevel {
|
||||
r.directRule.Add(domain)
|
||||
if r.PersistFn != nil {
|
||||
r.PersistFn(domain)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// detect and caculate direct connection and proxy connection score
|
||||
func (r *Route) detect(domain string) (http, https int) {
|
||||
wg := sync.WaitGroup{}
|
||||
httpScore, httpsScore := new(int32), new(int32)
|
||||
for _, ping := range [...]struct {
|
||||
shouldProxy bool
|
||||
port Port
|
||||
}{
|
||||
{shouldProxy: true, port: HTTP},
|
||||
{shouldProxy: true, port: HTTPS},
|
||||
{shouldProxy: false, port: HTTP},
|
||||
{shouldProxy: false, port: HTTPS},
|
||||
} {
|
||||
wg.Add(1)
|
||||
go func(shouldProxy bool, port Port) {
|
||||
defer wg.Done()
|
||||
|
||||
if err := port.Ping(domain, func(domain string) (net.Conn, error) {
|
||||
return transport.Dial(domain,
|
||||
func(domain string) (proxyAddr string, password []byte) {
|
||||
if shouldProxy {
|
||||
return r.ProxyAddress, r.password
|
||||
}
|
||||
return "", nil
|
||||
})
|
||||
}); err != nil {
|
||||
log.Warnw("sower dial", "proxy", shouldProxy, "host", domain, "port", port, "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
switch {
|
||||
case shouldProxy && port == HTTP:
|
||||
if !atomic.CompareAndSwapInt32(httpScore, 0, -2) {
|
||||
atomic.AddInt32(httpScore, -1)
|
||||
}
|
||||
case shouldProxy && port == HTTPS:
|
||||
if !atomic.CompareAndSwapInt32(httpsScore, 0, -2) {
|
||||
atomic.AddInt32(httpsScore, -1)
|
||||
}
|
||||
case !shouldProxy && port == HTTP:
|
||||
if !atomic.CompareAndSwapInt32(httpScore, 0, 2) {
|
||||
atomic.AddInt32(httpScore, 1)
|
||||
}
|
||||
case !shouldProxy && port == HTTPS:
|
||||
if !atomic.CompareAndSwapInt32(httpsScore, 0, 2) {
|
||||
atomic.AddInt32(httpsScore, 1)
|
||||
}
|
||||
}
|
||||
}(ping.shouldProxy, ping.port)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
return int(*httpScore), int(*httpsScore)
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package shadow
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
)
|
||||
|
||||
//go:generate stringer -type=cipherType $GOFILE
|
||||
type cipherType int
|
||||
|
||||
const (
|
||||
AES_128_GCM cipherType = iota
|
||||
AES_192_GCM
|
||||
AES_256_GCM
|
||||
CHACHA20_IETF_POLY1305
|
||||
XCHACHA20_IETF_POLY1305
|
||||
)
|
||||
|
||||
func pickCipher(cipherType, password string) (cipher.AEAD, error) {
|
||||
var blockSize int
|
||||
switch cipherType {
|
||||
case AES_128_GCM.String():
|
||||
blockSize = 16
|
||||
case AES_192_GCM.String():
|
||||
blockSize = 24
|
||||
case AES_256_GCM.String():
|
||||
blockSize = 32
|
||||
|
||||
case CHACHA20_IETF_POLY1305.String():
|
||||
return chacha20poly1305.New(genKey(password, 256))
|
||||
case XCHACHA20_IETF_POLY1305.String():
|
||||
return chacha20poly1305.NewX(genKey(password, 256))
|
||||
|
||||
default:
|
||||
return nil, errors.New("do not support cipher type: " + cipherType)
|
||||
}
|
||||
|
||||
// aes gcm
|
||||
block, err := aes.NewCipher(genKey(password, blockSize))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "password")
|
||||
}
|
||||
|
||||
aead, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, AES_128_GCM.String())
|
||||
}
|
||||
return aead, nil
|
||||
}
|
||||
|
||||
func genKey(filler string, size int) []byte {
|
||||
res := make([]byte, size)
|
||||
if filler == "" {
|
||||
panic("password should not be empty")
|
||||
}
|
||||
|
||||
fillerByte := []byte(filler)
|
||||
length := len(fillerByte)
|
||||
for i := 0; ; i++ {
|
||||
if copy(res[i*length:], fillerByte) != length {
|
||||
return res
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
// Code generated by "stringer -type=cipherType cipher.go"; DO NOT EDIT.
|
||||
|
||||
package shadow
|
||||
|
||||
import "strconv"
|
||||
|
||||
const _cipherType_name = "AES_128_GCMAES_192_GCMAES_256_GCMCHACHA20_IETF_POLY1305XCHACHA20_IETF_POLY1305"
|
||||
|
||||
var _cipherType_index = [...]uint8{0, 11, 22, 33, 55, 78}
|
||||
|
||||
func (i cipherType) String() string {
|
||||
if i < 0 || i >= cipherType(len(_cipherType_index)-1) {
|
||||
return "cipherType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _cipherType_name[_cipherType_index[i]:_cipherType_index[i+1]]
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
// Package shadow transter conn to be a crypto conn
|
||||
// support aead mode only
|
||||
// payload: [size](2+Overhead bytes) + [content](size+Overhead bytes)
|
||||
package shadow
|
||||
@@ -1,111 +0,0 @@
|
||||
package shadow
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
)
|
||||
|
||||
const MAX_SIZE = 0xFFFF
|
||||
|
||||
type conn struct {
|
||||
maxSize int
|
||||
aead cipher.AEAD
|
||||
encryptNonce func() []byte
|
||||
decryptNonce func() []byte
|
||||
writeBuf []byte
|
||||
readBuf []byte
|
||||
readOffset int
|
||||
net.Conn
|
||||
}
|
||||
|
||||
func (c *conn) Read(b []byte) (n int, err error) {
|
||||
// read from buffer
|
||||
if c.readOffset != 0 {
|
||||
dataSize := len(c.readBuf) - c.aead.Overhead()
|
||||
n = copy(b, c.readBuf[c.readOffset:dataSize])
|
||||
c.readOffset += n
|
||||
|
||||
if c.readOffset == dataSize {
|
||||
c.readOffset = 0
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// read from conn
|
||||
dataSize := 0
|
||||
{ //read data size
|
||||
c.readBuf = make([]byte, 2+c.aead.Overhead())
|
||||
if _, err = io.ReadFull(c.Conn, c.readBuf); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = c.aead.Open(c.readBuf[:0], c.decryptNonce(), c.readBuf, nil); err != nil {
|
||||
return
|
||||
}
|
||||
dataSize = int(c.readBuf[0])<<8 + int(c.readBuf[1])
|
||||
}
|
||||
{ // read data
|
||||
c.readBuf = make([]byte, dataSize+c.aead.Overhead())
|
||||
if _, err = io.ReadFull(c.Conn, c.readBuf); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = c.aead.Open(c.readBuf[:0], c.decryptNonce(), c.readBuf, nil); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// buffer extra data
|
||||
if n = copy(b, c.readBuf[:dataSize]); n < dataSize {
|
||||
c.readOffset = n
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *conn) Write(b []byte) (n int, err error) {
|
||||
bLen := len(b)
|
||||
dataSize := MAX_SIZE - (2 + c.aead.Overhead()) - c.aead.Overhead()
|
||||
if bLen < c.maxSize {
|
||||
dataSize = bLen
|
||||
}
|
||||
|
||||
// BigEndian
|
||||
c.writeBuf[0], c.writeBuf[1] = byte((dataSize&0xFF00)>>8), byte(dataSize&0xFF)
|
||||
|
||||
c.aead.Seal(c.writeBuf[:0], c.encryptNonce(), c.writeBuf[:2], nil)
|
||||
c.aead.Seal(c.writeBuf[:2+c.aead.Overhead()], c.encryptNonce(), b[:dataSize], nil)
|
||||
|
||||
_, err = c.Conn.Write(c.writeBuf[:dataSize+(2+c.aead.Overhead())+c.aead.Overhead()])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return dataSize, err
|
||||
}
|
||||
|
||||
func Shadow(c net.Conn, cipher, password string) net.Conn {
|
||||
aead, err := pickCipher(cipher, password)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return &conn{
|
||||
maxSize: MAX_SIZE - (2 - aead.Overhead()) - aead.Overhead(),
|
||||
aead: aead,
|
||||
encryptNonce: newNonce(password, aead.NonceSize()),
|
||||
decryptNonce: newNonce(password, aead.NonceSize()),
|
||||
writeBuf: make([]byte, 0xFFFF),
|
||||
Conn: c,
|
||||
}
|
||||
}
|
||||
|
||||
func newNonce(password string, size int) func() []byte {
|
||||
num, _ := binary.Varint([]byte(password))
|
||||
rnd := rand.New(rand.NewSource(num))
|
||||
|
||||
buf := make([]byte, size)
|
||||
return func() []byte {
|
||||
rnd.Read(buf)
|
||||
return buf
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/wweir/sower/util"
|
||||
)
|
||||
|
||||
// checksum(>=0x80) + port + target_length + target + data
|
||||
// data(HTTP, first byte < 0x7F)
|
||||
type head struct {
|
||||
Checksum byte
|
||||
Port uint16
|
||||
AddrLen uint8
|
||||
}
|
||||
|
||||
func ParseProxyConn(conn net.Conn, password []byte) (net.Conn, string) {
|
||||
teeConn := &util.TeeConn{Conn: conn}
|
||||
defer teeConn.Stop()
|
||||
|
||||
h := &head{}
|
||||
if err := binary.Read(teeConn, binary.BigEndian, h); err != nil || h.Checksum < 0x80 {
|
||||
return teeConn, ""
|
||||
}
|
||||
|
||||
buf := make([]byte, int(h.AddrLen))
|
||||
if _, err := io.ReadFull(teeConn, buf); err != nil {
|
||||
return teeConn, ""
|
||||
}
|
||||
|
||||
if h.Checksum != sumChecksum(buf, password) {
|
||||
return teeConn, ""
|
||||
}
|
||||
|
||||
teeConn.Reset()
|
||||
return teeConn, net.JoinHostPort(string(buf), strconv.Itoa(int(h.Port)))
|
||||
}
|
||||
|
||||
func ToProxyConn(conn net.Conn, tgtHost string, tgtPort uint16, password []byte) (net.Conn, error) {
|
||||
h := &head{
|
||||
Checksum: sumChecksum([]byte(tgtHost), password),
|
||||
Port: tgtPort,
|
||||
AddrLen: uint8(len(tgtHost)),
|
||||
}
|
||||
if err := binary.Write(conn, binary.BigEndian, h); err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var data = []byte(tgtHost)
|
||||
var err error
|
||||
for n, nn := 0, 0; nn < len(data); nn += n {
|
||||
if n, err = conn.Write(data[nn:]); err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func sumChecksum(target, password []byte) byte {
|
||||
return md5.Sum(append(target, password...))[0] | 0x80
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
func IsSocks5Schema(addr string) (string, bool) {
|
||||
if strings.HasPrefix(addr, "socks5://") {
|
||||
return strings.TrimPrefix(addr, "socks5://"), true
|
||||
}
|
||||
|
||||
if strings.HasPrefix(addr, "socks5h://") {
|
||||
return strings.TrimPrefix(addr, "socks5h://"), true
|
||||
}
|
||||
|
||||
return addr, false
|
||||
}
|
||||
|
||||
func ToSocks5(c net.Conn, host string, port uint16) (net.Conn, error) {
|
||||
return &conn{
|
||||
init: make(chan struct{}),
|
||||
Conn: c,
|
||||
domain: host,
|
||||
port: port,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type conn struct {
|
||||
init chan struct{}
|
||||
domain string
|
||||
port uint16
|
||||
net.Conn
|
||||
}
|
||||
|
||||
func (c *conn) Read(b []byte) (n int, err error) {
|
||||
select {
|
||||
case <-c.init:
|
||||
return c.Conn.Read(b)
|
||||
default:
|
||||
return 0, c.clientHandshake()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *conn) Write(b []byte) (n int, err error) {
|
||||
select {
|
||||
case <-c.init:
|
||||
return c.Conn.Write(b)
|
||||
default:
|
||||
return 0, c.clientHandshake()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *conn) clientHandshake() error {
|
||||
{
|
||||
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 xerrors.New(err.Error())
|
||||
}
|
||||
}
|
||||
{
|
||||
resp := &authResp{}
|
||||
if err := binary.Read(c.Conn, binary.BigEndian, resp); err != nil {
|
||||
return xerrors.New(err.Error())
|
||||
}
|
||||
}
|
||||
{
|
||||
reqHead := requestHead{
|
||||
VER: 5, // socks5
|
||||
CMD: 1, // CONNECT
|
||||
RSV: 0, // RESERVED
|
||||
ATYP: 3, // DOMAINNAME
|
||||
}
|
||||
|
||||
if err := binary.Write(c.Conn, binary.BigEndian, reqHead); err != nil {
|
||||
return xerrors.New(err.Error())
|
||||
}
|
||||
|
||||
buf := make([]byte, 0, 1 /*LEN*/ +len(c.domain)+2 /*PORT*/)
|
||||
buf = append(buf, byte(len(c.domain)))
|
||||
buf = append(buf, []byte(c.domain)...)
|
||||
buf = append(buf, byte(c.port>>8), byte(c.port))
|
||||
if _, err := c.Conn.Write(buf); err != nil {
|
||||
return xerrors.New(err.Error())
|
||||
}
|
||||
}
|
||||
{
|
||||
head := replyHead{}
|
||||
if err := binary.Read(c.Conn, binary.BigEndian, &head); err != nil {
|
||||
return xerrors.New(err.Error())
|
||||
}
|
||||
|
||||
switch head.REP {
|
||||
case 0x00:
|
||||
default:
|
||||
return xerrors.Errorf("socks5 handshake fail, return code: %d", head.REP)
|
||||
}
|
||||
|
||||
var bindAddr addrType
|
||||
switch head.ATYP {
|
||||
case 0x01: // IPv4
|
||||
bindAddr = addrTypeIPv4{}
|
||||
case 0x03: // domain name
|
||||
bindAddr = addrTypeDomain{}
|
||||
case 0x04: // IPv6
|
||||
bindAddr = addrTypeIPv6{}
|
||||
default:
|
||||
return xerrors.New("invalid connect type")
|
||||
}
|
||||
if err := bindAddr.Fullfill(c.Conn); err != nil {
|
||||
return xerrors.New(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
close(c.init)
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// https://tools.ietf.org/html/rfc1928
|
||||
|
||||
// 1. client send auth request
|
||||
type authReq struct {
|
||||
VER byte
|
||||
NMETHODS byte
|
||||
METHODS [1]byte // 1 to 255, fix to no authentication
|
||||
}
|
||||
|
||||
// 2. server response auth request
|
||||
type authResp struct {
|
||||
VER byte
|
||||
METHOD byte
|
||||
}
|
||||
|
||||
// 3. client request with target address
|
||||
type requestHead struct {
|
||||
VER byte
|
||||
CMD byte
|
||||
RSV byte
|
||||
ATYP byte
|
||||
}
|
||||
|
||||
// 4. server response with the address that assigned to connect to target address
|
||||
type replyHead struct {
|
||||
VER byte
|
||||
REP byte
|
||||
RSV byte
|
||||
ATYP byte
|
||||
}
|
||||
|
||||
type addrType interface {
|
||||
Fullfill(r io.Reader) error
|
||||
String() string
|
||||
}
|
||||
|
||||
// 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) Fullfill(r io.Reader) error {
|
||||
return binary.Read(r, binary.BigEndian, &a)
|
||||
}
|
||||
func (a addrTypeIPv4) String() string {
|
||||
return net.JoinHostPort(
|
||||
net.IP(a.DST_ADDR[:]).String(),
|
||||
strconv.FormatUint(uint64(a.DST_PORT), 10),
|
||||
)
|
||||
}
|
||||
|
||||
type addrTypeDomain struct {
|
||||
DST_ADDR_LEN uint8
|
||||
DST_ADDR []byte
|
||||
DST_PORT uint16
|
||||
}
|
||||
|
||||
func (a addrTypeDomain) Fullfill(r io.Reader) error {
|
||||
buf := make([]byte, 256)
|
||||
// domain length
|
||||
if _, err := io.ReadFull(r, buf[:1]); err != nil {
|
||||
return xerrors.New(err.Error())
|
||||
}
|
||||
a.DST_ADDR_LEN = uint8(buf[0])
|
||||
|
||||
// domain
|
||||
if _, err := io.ReadFull(r, buf[:int(buf[0])]); err != nil {
|
||||
return xerrors.New(err.Error())
|
||||
}
|
||||
a.DST_ADDR = buf[:int(buf[0])]
|
||||
|
||||
// port
|
||||
if _, err := io.ReadFull(r, buf[:2]); err != nil {
|
||||
return xerrors.New(err.Error())
|
||||
}
|
||||
a.DST_PORT = binary.BigEndian.Uint16(buf[:2])
|
||||
|
||||
return nil
|
||||
}
|
||||
func (a addrTypeDomain) String() string {
|
||||
return net.JoinHostPort(
|
||||
net.IP(a.DST_ADDR[:]).String(),
|
||||
strconv.FormatUint(uint64(a.DST_PORT), 10),
|
||||
)
|
||||
}
|
||||
|
||||
type addrTypeIPv6 struct {
|
||||
DST_ADDR [16]byte
|
||||
DST_PORT uint16
|
||||
}
|
||||
|
||||
func (a addrTypeIPv6) Fullfill(r io.Reader) error {
|
||||
return binary.Read(r, binary.BigEndian, &a)
|
||||
}
|
||||
func (a addrTypeIPv6) String() string {
|
||||
return net.JoinHostPort(
|
||||
net.IP(a.DST_ADDR[:]).String(),
|
||||
strconv.FormatUint(uint64(a.DST_PORT), 10),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"net"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
func ParseSocks5(conn net.Conn) (tgtaddr string, err error) {
|
||||
{
|
||||
authReq := new(authReq)
|
||||
if err := binary.Read(conn, binary.BigEndian, authReq); err != nil {
|
||||
return "", xerrors.New(err.Error())
|
||||
}
|
||||
|
||||
if authReq.VER != 5 || // socks5
|
||||
authReq.NMETHODS != 1 ||
|
||||
authReq.METHODS[0] != 0 { // NO_AUTH
|
||||
return "", xerrors.New("invalid socks5 auth method")
|
||||
}
|
||||
}
|
||||
{
|
||||
if err := binary.Write(conn, binary.BigEndian, &authResp{
|
||||
VER: 5,
|
||||
METHOD: 1,
|
||||
}); err != nil {
|
||||
return "", xerrors.New(err.Error())
|
||||
}
|
||||
}
|
||||
{
|
||||
head := &requestHead{}
|
||||
if err := binary.Read(conn, binary.BigEndian, head); err != nil {
|
||||
return "", xerrors.New(err.Error())
|
||||
}
|
||||
if head.VER != 5 ||
|
||||
head.CMD != 1 {
|
||||
return "", xerrors.New("invalid socks5 connect request")
|
||||
}
|
||||
|
||||
var addr addrType
|
||||
switch head.ATYP {
|
||||
case 0x01: // IPv4
|
||||
addr = addrTypeIPv4{}
|
||||
case 0x03: // domain name
|
||||
addr = addrTypeDomain{}
|
||||
case 0x04: // IPv6
|
||||
addr = addrTypeIPv6{}
|
||||
default:
|
||||
return "", xerrors.New("invalid connect type")
|
||||
}
|
||||
if err := addr.Fullfill(conn); err != nil {
|
||||
return "", xerrors.New(err.Error())
|
||||
}
|
||||
tgtaddr = addr.String()
|
||||
}
|
||||
{
|
||||
if err := binary.Write(conn, binary.BigEndian, &replyHead{
|
||||
VER: 5,
|
||||
REP: 0,
|
||||
RSV: 0,
|
||||
ATYP: 1,
|
||||
}); err != nil {
|
||||
return "", xerrors.New(err.Error())
|
||||
}
|
||||
|
||||
// FIXME: return the real address
|
||||
if _, err := conn.Write(make([]byte, 6)); err != nil {
|
||||
return "", xerrors.New(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return tgtaddr, nil
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/wweir/sower/util"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// +-----------------------+---------+----------------+---------+----------+
|
||||
// | 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'
|
||||
|
||||
type Trojan struct {
|
||||
Password [56]byte
|
||||
TrojanRequest
|
||||
}
|
||||
type TrojanRequest struct {
|
||||
CMD uint8
|
||||
ATYP uint8
|
||||
DstAddr []byte
|
||||
DstPort uint16
|
||||
}
|
||||
|
||||
func (t *TrojanRequest) Addr() string {
|
||||
switch t.ATYP {
|
||||
case 0x01:
|
||||
fallthrough
|
||||
case 0x04:
|
||||
return net.JoinHostPort(net.IP(t.DstAddr).String(), strconv.Itoa(int(t.DstPort)))
|
||||
case 0x03:
|
||||
return string(t.DstAddr) + ":" + strconv.Itoa(int(t.DstPort))
|
||||
default:
|
||||
panic("invalid ATYP")
|
||||
}
|
||||
}
|
||||
|
||||
func ParseTrojanConn(conn net.Conn, password []byte) (net.Conn, *Trojan) {
|
||||
passData := sha256.Sum224(password)
|
||||
passHead := hex.EncodeToString(passData[:])
|
||||
|
||||
teeConn := &util.TeeConn{Conn: conn}
|
||||
defer teeConn.Stop()
|
||||
|
||||
buf := make([]byte, 56+2+1+1+1)
|
||||
if _, err := io.ReadFull(teeConn, buf); err != nil {
|
||||
return teeConn, nil
|
||||
}
|
||||
if string(buf[:56]) != passHead {
|
||||
return teeConn, nil
|
||||
}
|
||||
|
||||
t := Trojan{}
|
||||
t.CMD, t.ATYP = buf[58], buf[59]
|
||||
addrLen := buf[60]
|
||||
switch t.ATYP {
|
||||
case 0x01: //ipv4
|
||||
buf = make([]byte, net.IPv4len+2+2)
|
||||
buf[0] = addrLen
|
||||
if _, err := io.ReadFull(teeConn, buf[1:]); err != nil {
|
||||
return teeConn, nil
|
||||
}
|
||||
|
||||
case 0x04: //ipv6
|
||||
buf = make([]byte, net.IPv6len+2+2)
|
||||
buf[0] = addrLen
|
||||
if _, err := io.ReadFull(teeConn, buf[1:]); err != nil {
|
||||
return teeConn, nil
|
||||
}
|
||||
|
||||
case 0x03: // domain
|
||||
buf = make([]byte, addrLen+2+2)
|
||||
buf[0] = addrLen
|
||||
if _, err := io.ReadFull(teeConn, buf); err != nil {
|
||||
return teeConn, nil
|
||||
}
|
||||
default:
|
||||
return teeConn, nil
|
||||
}
|
||||
|
||||
t.DstAddr = buf[:len(buf)-4]
|
||||
t.DstPort = uint16(buf[len(buf)-4])<<8 + uint16(buf[len(buf)-3])
|
||||
|
||||
teeConn.Reset()
|
||||
return teeConn, &t
|
||||
}
|
||||
|
||||
func ToTrojanConn(conn net.Conn, tgtHost string, tgtPort uint16, password []byte) (net.Conn, error) {
|
||||
passData := sha256.Sum224(password)
|
||||
passHead := hex.EncodeToString(passData[:])
|
||||
|
||||
buf := make([]byte, 0, 56+2+1+1+1+len(tgtHost)+2+2)
|
||||
buf = append(buf, []byte(passHead)...)
|
||||
buf = append(buf, '\r', '\n')
|
||||
buf = append(buf, 1, 3, uint8(len(tgtHost)))
|
||||
buf = append(buf, []byte(tgtHost)...)
|
||||
buf = append(buf, byte(tgtPort>>8), byte(tgtPort))
|
||||
buf = append(buf, '\r', '\n')
|
||||
|
||||
if n, err := conn.Write(buf); err != nil || n != len(buf) {
|
||||
return conn, xerrors.Errorf("n: %d, msg: %s", n, err)
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/wweir/sower/dhcp"
|
||||
"github.com/wweir/util-go/log"
|
||||
)
|
||||
|
||||
var (
|
||||
persistDNS string
|
||||
dnsAddr string
|
||||
resolver = &net.Resolver{
|
||||
PreferGo: true,
|
||||
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
return (&net.Dialer{}).DialContext(ctx, network, dnsAddr)
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func SetDNS(err error, dnsIP string) {
|
||||
if dnsIP != "" {
|
||||
persistDNS = dnsIP
|
||||
dnsAddr = net.JoinHostPort(dnsIP, "53")
|
||||
return
|
||||
} else if persistDNS != "" {
|
||||
return
|
||||
}
|
||||
|
||||
if e, ok := err.(*net.DNSError); !ok /*nil*/ || !e.IsNotFound {
|
||||
if dnsIP, err = dhcp.GetDefaultDNSServer(); err != nil {
|
||||
dnsIP, err = dhcp.GetDefaultDNSServer() // retry
|
||||
}
|
||||
if err != nil {
|
||||
log.Errorw("get dns via dhcp", "err", err, "current_dns", dnsAddr)
|
||||
} else {
|
||||
dnsAddr = net.JoinHostPort(dnsIP, "53")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dial dial targetAddr with possiable proxy address
|
||||
func Dial(targetAddr string, dialAddr func(domain string) (proxyAddr string, password []byte)) (net.Conn, error) {
|
||||
host, port, err := net.SplitHostPort(targetAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
address, password := dialAddr(host)
|
||||
if address == "" {
|
||||
ips, err := resolver.LookupIPAddr(context.Background(), host)
|
||||
if err != nil { //retry
|
||||
ips, err = resolver.LookupIPAddr(context.Background(), host)
|
||||
}
|
||||
if err != nil {
|
||||
SetDNS(err, "")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return net.Dial("tcp", net.JoinHostPort(ips[0].String(), port))
|
||||
}
|
||||
|
||||
p, err := strconv.Atoi(port)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if addr, ok := IsSocks5Schema(address); ok {
|
||||
conn, err := net.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if conn, err = ToSocks5(conn, host, uint16(p)); err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
conn, err := tls.DialWithDialer(&net.Dialer{Resolver: resolver},
|
||||
"tcp", net.JoinHostPort(address, "443"), &tls.Config{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ToTrojanConn(conn, host, uint16(p), password)
|
||||
}
|
||||
+15
-8
@@ -14,11 +14,8 @@ type node struct {
|
||||
node map[string]*node
|
||||
}
|
||||
|
||||
func NewNode(sep string) *Node {
|
||||
return &Node{node{node: map[string]*node{}}, sep, &sync.RWMutex{}}
|
||||
}
|
||||
func NewNodeFromRules(sep string, rules ...string) *Node {
|
||||
n := NewNode(sep)
|
||||
func NewNodeFromRules(rules ...string) *Node {
|
||||
n := &Node{node{node: map[string]*node{}}, ".", &sync.RWMutex{}}
|
||||
for i := range rules {
|
||||
n.Add(rules[i])
|
||||
}
|
||||
@@ -52,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 == "**" {
|
||||
@@ -69,6 +66,12 @@ func (n *node) add(secs []string) {
|
||||
}
|
||||
|
||||
func (n *Node) Match(item string) bool {
|
||||
if n == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
n.RLock()
|
||||
defer n.RUnlock()
|
||||
return n.matchSecs(strings.Split(n.trim(item), n.sep), false)
|
||||
}
|
||||
|
||||
@@ -88,10 +91,14 @@ func (n *node) matchSecs(secs []string, fuzzNode bool) bool {
|
||||
}
|
||||
|
||||
if n, ok := n.node[secs[length-1]]; ok {
|
||||
return n.matchSecs(secs[:length-1], false)
|
||||
if n.matchSecs(secs[:length-1], false) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if n, ok := n.node["*"]; ok {
|
||||
return n.matchSecs(secs[:length-1], true)
|
||||
if n.matchSecs(secs[:length-1], true) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if _, ok := n.node["**"]; ok {
|
||||
return true
|
||||
|
||||
+14
-10
@@ -1,7 +1,9 @@
|
||||
package util
|
||||
package util_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/wweir/sower/util"
|
||||
)
|
||||
|
||||
func TestNode_Match(t *testing.T) {
|
||||
@@ -11,18 +13,18 @@ func TestNode_Match(t *testing.T) {
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
node *Node
|
||||
node *util.Node
|
||||
tests []test
|
||||
}{{
|
||||
"simple",
|
||||
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",
|
||||
NewNodeFromRules(".", "wweir.cc", "a.wweir.cc"),
|
||||
util.NewNodeFromRules("wweir.cc", "a.wweir.cc"),
|
||||
[]test{
|
||||
{"wweir.cc", true},
|
||||
{"a.wweir.cc", true},
|
||||
@@ -30,7 +32,7 @@ func TestNode_Match(t *testing.T) {
|
||||
},
|
||||
}, {
|
||||
"fuzz1",
|
||||
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},
|
||||
@@ -39,15 +41,16 @@ func TestNode_Match(t *testing.T) {
|
||||
},
|
||||
}, {
|
||||
"fuzz2",
|
||||
NewNodeFromRules(".", "a.*.cc"),
|
||||
util.NewNodeFromRules("a.*.cc", "c.wweir.*"),
|
||||
[]test{
|
||||
{"wweir.cc", false},
|
||||
{"a.wweir.cc", true},
|
||||
{"b.wweir.cc", false},
|
||||
{"c.wweir.cc", true},
|
||||
},
|
||||
}, {
|
||||
"fuzz3",
|
||||
NewNodeFromRules(".", "*.*.cc", "iamp.*.*"),
|
||||
util.NewNodeFromRules("*.*.cc", "iamp.*.*"),
|
||||
[]test{
|
||||
{"wweir.cc", false},
|
||||
{"a.wweir.cc", true},
|
||||
@@ -56,13 +59,14 @@ func TestNode_Match(t *testing.T) {
|
||||
},
|
||||
}, {
|
||||
"fuzz4",
|
||||
NewNodeFromRules(".", "**.cc", "a.**.com"),
|
||||
util.NewNodeFromRules("**.cc", "a.**.com", "**.wweir.*"),
|
||||
[]test{
|
||||
{"wweir.cc", true},
|
||||
{"a.wweir.cc", true},
|
||||
{"a.b.wweir.cc", true},
|
||||
{"a.wweir.com", true},
|
||||
{"b.wweir.com", false},
|
||||
{"a.fuzz.com", true},
|
||||
{"b.fuzz.com", false},
|
||||
{"www.wweir.com", true},
|
||||
},
|
||||
}}
|
||||
for _, tt := range tests {
|
||||
|
||||
+20
-7
@@ -1,23 +1,28 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
)
|
||||
|
||||
type TeeConn struct {
|
||||
net.Conn
|
||||
buf []byte
|
||||
offset int
|
||||
tee bool // read
|
||||
buf []byte
|
||||
offset int
|
||||
stop bool // read
|
||||
EnableWrite bool
|
||||
}
|
||||
|
||||
func (t *TeeConn) StartOrReset() {
|
||||
func (t *TeeConn) Reread() {
|
||||
t.offset = 0
|
||||
}
|
||||
func (t *TeeConn) Reset() {
|
||||
t.buf = []byte{}
|
||||
t.offset = 0
|
||||
t.tee = true
|
||||
}
|
||||
func (t *TeeConn) Stop() {
|
||||
t.offset = 0
|
||||
t.tee = false
|
||||
t.stop = true
|
||||
}
|
||||
|
||||
func (t *TeeConn) Read(b []byte) (n int, err error) {
|
||||
@@ -29,9 +34,17 @@ func (t *TeeConn) Read(b []byte) (n int, err error) {
|
||||
}
|
||||
|
||||
n, err = t.Conn.Read(b)
|
||||
if t.tee {
|
||||
if !t.stop {
|
||||
t.buf = append(t.buf, b[:n]...)
|
||||
t.offset += n
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (t *TeeConn) Write(b []byte) (n int, err error) {
|
||||
if t.stop || t.EnableWrite {
|
||||
return t.Conn.Write(b)
|
||||
}
|
||||
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package util
|
||||
|
||||
import "net"
|
||||
|
||||
func WithDefaultPort(addr string, port string) (address, host string) {
|
||||
host, _, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return net.JoinHostPort(addr, port), addr
|
||||
}
|
||||
return addr, host
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
func WithTimeout(fn func() error, timeout time.Duration) error {
|
||||
var okCh = make(chan struct{})
|
||||
var err error
|
||||
|
||||
go func() {
|
||||
err = fn()
|
||||
close(okCh)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-okCh:
|
||||
return err
|
||||
case <-time.After(timeout):
|
||||
return errors.New("timeout: " + timeout.String())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user