mirror of
https://github.com/nadoo/glider.git
synced 2025-04-22 04:02:07 +08:00
init
This commit is contained in:
commit
be63cb624c
39
.github/workflows/build.yml
vendored
Normal file
39
.github/workflows/build.yml
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
name: Build
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
|
||||
test:
|
||||
name: Test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- name: Set up Go 1.13
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: 1.13
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Test
|
||||
run: go test -v .
|
||||
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
needs: [test]
|
||||
steps:
|
||||
|
||||
- name: Set up Go 1.13
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: 1.13
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Build
|
||||
run: go build -v .
|
22
.github/workflows/release.yml
vendored
Normal file
22
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
name: Release
|
||||
on:
|
||||
create:
|
||||
tags:
|
||||
- v*
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Release on GitHub
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Create release on GitHub
|
||||
uses: docker://goreleaser/goreleaser:latest
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||
with:
|
||||
args: release
|
||||
if: success()
|
||||
|
32
.gitignore
vendored
Normal file
32
.gitignore
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
||||
.glide/
|
||||
|
||||
# custom
|
||||
.idea
|
||||
.vscode
|
||||
|
||||
dist
|
||||
|
||||
*.zip
|
||||
/*.conf
|
||||
/*.rule
|
||||
|
||||
config/rules.d/*.rule
|
||||
config/rules.d/*.list
|
||||
|
||||
glider
|
||||
/bak/
|
||||
/rules.d/
|
62
.goreleaser.yml
Normal file
62
.goreleaser.yml
Normal file
@ -0,0 +1,62 @@
|
||||
# Make sure to check the documentation at http://goreleaser.com
|
||||
|
||||
# release:
|
||||
# git tag -a v0.1.0 -m "v0.1.0"
|
||||
# git push origin v0.1.0
|
||||
# goreleaser release --skip-publish --rm-dist
|
||||
|
||||
# snapshot:
|
||||
# goreleaser --snapshot --rm-dist
|
||||
|
||||
# https://goreleaser.com/customization/
|
||||
|
||||
before:
|
||||
hooks:
|
||||
- go mod tidy
|
||||
|
||||
# https://goreleaser.com/build/
|
||||
builds:
|
||||
- env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- windows
|
||||
- linux
|
||||
- darwin
|
||||
goarch:
|
||||
- 386
|
||||
- amd64
|
||||
- arm
|
||||
- arm64
|
||||
- mips
|
||||
- mipsle
|
||||
- mips64
|
||||
- mips64le
|
||||
goarm:
|
||||
- 6
|
||||
- 7
|
||||
|
||||
ignore:
|
||||
- goos: darwin
|
||||
goarch: 386
|
||||
|
||||
# https://goreleaser.com/archive/
|
||||
archive:
|
||||
name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
|
||||
wrap_in_directory: true
|
||||
format: tar.gz
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
files:
|
||||
- LICENSE
|
||||
- README.md
|
||||
- config/**/*
|
||||
- systemd/*
|
||||
|
||||
# https://goreleaser.com/snapshots/
|
||||
snapshot:
|
||||
name_template: "dev@{{.ShortCommit}}"
|
||||
|
||||
# https://goreleaser.com/checksum/
|
||||
checksum:
|
||||
name_template: "{{ .ProjectName }}_{{ .Version }}_checksums.txt"
|
674
LICENSE
Normal file
674
LICENSE
Normal file
@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
{one line to give the program's name and a brief idea of what it does.}
|
||||
Copyright (C) {year} {name of author}
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
{project} Copyright (C) {year} {fullname}
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
385
README.md
Normal file
385
README.md
Normal file
@ -0,0 +1,385 @@
|
||||
# [glider](https://github.com/nadoo/glider)
|
||||
|
||||
[](https://goreportcard.com/report/github.com/nadoo/glider)
|
||||
[](https://github.com/nadoo/glider/releases)
|
||||
|
||||
glider is a forward proxy with multiple protocols support, and also a dns forwarding server with ipset management features(like dnsmasq).
|
||||
|
||||
we can set up local listeners as proxy servers, and forward requests to internet via forwarders.
|
||||
|
||||
```bash
|
||||
|Forwarder ----------------->|
|
||||
Listener --> | | Internet
|
||||
|Forwarder --> Forwarder->...|
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
Listen (local proxy server):
|
||||
|
||||
- Socks5 proxy(tcp&udp)
|
||||
- Http proxy(tcp)
|
||||
- SS proxy(tcp&udp)
|
||||
- Linux transparent proxy(iptables redirect)
|
||||
- TCP tunnel
|
||||
- UDP tunnel
|
||||
- UDP over TCP tunnel
|
||||
- TLS, use it together with above proxy protocols(tcp)
|
||||
- Unix domain socket, use it together with above proxy protocols(tcp)
|
||||
- KCP protocol, use it together with above proxy protocols(tcp)
|
||||
|
||||
Forward (local proxy client/upstream proxy server):
|
||||
|
||||
- Socks5 proxy(tcp&udp)
|
||||
- Http proxy(tcp)
|
||||
- SS proxy(tcp&udp&uot)
|
||||
- SSR proxy(tcp)
|
||||
- VMess proxy(tcp)
|
||||
- TLS, use it together with above proxy protocols(tcp)
|
||||
- Websocket, use it together with above proxy protocols(tcp)
|
||||
- Unix domain socket, use it together with above proxy protocols(tcp)
|
||||
- KCP protocol, use it together with above proxy protocols(tcp)
|
||||
- Simple-Obfs, use it together with above proxy protocols(tcp)
|
||||
|
||||
DNS Forwarding Server (udp2tcp):
|
||||
|
||||
- Listen on UDP and forward dns requests to remote dns server in TCP via forwarders
|
||||
- Specify different upstream dns server based on destinations(in rule file)
|
||||
- Tunnel mode: forward to a fixed upstream dns server
|
||||
- Add resolved IPs to proxy rules
|
||||
- Add resolved IPs to ipset
|
||||
- DNS cache
|
||||
- Custom dns record
|
||||
|
||||
IPSet Management (Linux kernel version >= 2.6.32):
|
||||
|
||||
- Add ip/cidrs from rule files on startup
|
||||
- Add resolved ips for domains from rule files by dns forwarding server
|
||||
|
||||
General:
|
||||
|
||||
- Http and socks5 on the same port
|
||||
- Forwarder chain
|
||||
- RR/HA/LHA/DH strategy for multiple forwarders
|
||||
- Periodical proxy checking
|
||||
- Rule proxy based on destinations: [Config Examples](config/examples)
|
||||
- Send requests from specific ip/interface
|
||||
|
||||
TODO:
|
||||
|
||||
- [ ] IPv6 support in ipset manager
|
||||
- [ ] Transparent UDP proxy (iptables tproxy)
|
||||
- [ ] Performance tuning
|
||||
- [ ] TUN/TAP device support
|
||||
- [ ] SSH tunnel support (maybe)
|
||||
|
||||
## Install
|
||||
|
||||
Binary:
|
||||
|
||||
- [https://github.com/nadoo/glider/releases](https://github.com/nadoo/glider/releases)
|
||||
|
||||
Go Get (requires **Go 1.13+** ):
|
||||
|
||||
```bash
|
||||
go get -u github.com/nadoo/glider
|
||||
```
|
||||
|
||||
ArchLinux:
|
||||
|
||||
```bash
|
||||
sudo pacman -S glider
|
||||
```
|
||||
|
||||
## Run
|
||||
|
||||
command line:
|
||||
|
||||
```bash
|
||||
glider -listen :8443 -verbose
|
||||
```
|
||||
|
||||
config file:
|
||||
|
||||
```bash
|
||||
glider -config CONFIGPATH
|
||||
```
|
||||
|
||||
command line with config file:
|
||||
|
||||
```bash
|
||||
glider -config CONFIGPATH -listen :8080 -verbose
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
glider 0.8.0 usage:
|
||||
-checkinterval int
|
||||
proxy check interval(seconds) (default 30)
|
||||
-checktimeout int
|
||||
proxy check timeout(seconds) (default 10)
|
||||
-checkwebsite string
|
||||
proxy check HTTP(NOT HTTPS) website address, format: HOST[:PORT], default port: 80 (default "www.apple.com")
|
||||
-config string
|
||||
config file path
|
||||
-dns string
|
||||
local dns server listen address
|
||||
-dnsalwaystcp
|
||||
always use tcp to query upstream dns servers no matter there is a forwarder or not
|
||||
-dnsmaxttl int
|
||||
maximum TTL value for entries in the CACHE(seconds) (default 1800)
|
||||
-dnsminttl int
|
||||
minimum TTL value for entries in the CACHE(seconds)
|
||||
-dnsrecord value
|
||||
custom dns record, format: domain/ip
|
||||
-dnsserver value
|
||||
remote dns server address
|
||||
-dnstimeout int
|
||||
timeout value used in multiple dnsservers switch(seconds) (default 3)
|
||||
-forward value
|
||||
forward url, format: SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS[,SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS]
|
||||
-include value
|
||||
include file
|
||||
-interface string
|
||||
source ip or source interface
|
||||
-listen value
|
||||
listen url, format: SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS
|
||||
-maxfailures int
|
||||
max failures to change forwarder status to disabled (default 3)
|
||||
-rulefile value
|
||||
rule file path
|
||||
-rules-dir string
|
||||
rule file folder
|
||||
-strategy string
|
||||
forward strategy, default: rr (default "rr")
|
||||
-verbose
|
||||
verbose mode
|
||||
|
||||
Available Schemes:
|
||||
mixed: serve as a http/socks5 proxy on the same port. (default)
|
||||
ss: ss proxy
|
||||
socks5: socks5 proxy
|
||||
http: http proxy
|
||||
ssr: ssr proxy
|
||||
vmess: vmess proxy
|
||||
tls: tls transport
|
||||
ws: websocket transport
|
||||
redir: redirect proxy. (used on linux as a transparent proxy with iptables redirect rules)
|
||||
redir6: redirect proxy(ipv6)
|
||||
tcptun: tcp tunnel
|
||||
udptun: udp tunnel
|
||||
uottun: udp over tcp tunnel
|
||||
unix: unix domain socket
|
||||
kcp: kcp protocol
|
||||
simple-obfs: simple-obfs protocol
|
||||
reject: a virtual proxy which just reject connections
|
||||
|
||||
Available schemes for different modes:
|
||||
listen: mixed ss socks5 http redir redir6 tcptun udptun uottun tls unix kcp
|
||||
forward: reject ss socks5 http ssr vmess tls ws unix kcp simple-obfs
|
||||
|
||||
SS scheme:
|
||||
ss://method:pass@host:port
|
||||
|
||||
Available methods for ss:
|
||||
AEAD Ciphers:
|
||||
AEAD_AES_128_GCM AEAD_AES_192_GCM AEAD_AES_256_GCM AEAD_CHACHA20_POLY1305 AEAD_XCHACHA20_POLY1305
|
||||
Stream Ciphers:
|
||||
AES-128-CFB AES-128-CTR AES-192-CFB AES-192-CTR AES-256-CFB AES-256-CTR CHACHA20-IETF XCHACHA20 CHACHA20 RC4-MD5
|
||||
Alias:
|
||||
chacha20-ietf-poly1305 = AEAD_CHACHA20_POLY1305, xchacha20-ietf-poly1305 = AEAD_XCHACHA20_POLY1305
|
||||
|
||||
SSR scheme:
|
||||
ssr://method:pass@host:port?protocol=xxx&protocol_param=yyy&obfs=zzz&obfs_param=xyz
|
||||
|
||||
VMess scheme:
|
||||
vmess://[security:]uuid@host:port?alterID=num
|
||||
|
||||
Available securities for vmess:
|
||||
none, aes-128-gcm, chacha20-poly1305
|
||||
|
||||
TLS client scheme:
|
||||
tls://host:port[?skipVerify=true]
|
||||
|
||||
Proxy over tls client:
|
||||
tls://host:port[?skipVerify=true],scheme://
|
||||
tls://host:port[?skipVerify=true],http://[user:pass@]
|
||||
tls://host:port[?skipVerify=true],socks5://[user:pass@]
|
||||
tls://host:port[?skipVerify=true],vmess://[security:]uuid@?alterID=num
|
||||
|
||||
TLS server scheme:
|
||||
tls://host:port?cert=PATH&key=PATH
|
||||
|
||||
Proxy over tls server:
|
||||
tls://host:port?cert=PATH&key=PATH,scheme://
|
||||
tls://host:port?cert=PATH&key=PATH,http://
|
||||
tls://host:port?cert=PATH&key=PATH,socks5://
|
||||
tls://host:port?cert=PATH&key=PATH,ss://method:pass@
|
||||
|
||||
Websocket scheme:
|
||||
ws://host:port[/path]
|
||||
|
||||
Websocket with a specified proxy protocol:
|
||||
ws://host:port[/path],scheme://
|
||||
ws://host:port[/path],http://[user:pass@]
|
||||
ws://host:port[/path],socks5://[user:pass@]
|
||||
ws://host:port[/path],vmess://[security:]uuid@?alterID=num
|
||||
|
||||
TLS and Websocket with a specified proxy protocol:
|
||||
tls://host:port[?skipVerify=true],ws://[@/path],scheme://
|
||||
tls://host:port[?skipVerify=true],ws://[@/path],http://[user:pass@]
|
||||
tls://host:port[?skipVerify=true],ws://[@/path],socks5://[user:pass@]
|
||||
tls://host:port[?skipVerify=true],ws://[@/path],vmess://[security:]uuid@?alterID=num
|
||||
|
||||
Unix domain socket scheme:
|
||||
unix://path
|
||||
|
||||
KCP scheme:
|
||||
kcp://CRYPT:KEY@host:port[?dataShards=NUM&parityShards=NUM]
|
||||
|
||||
Available crypt types for KCP:
|
||||
none, sm4, tea, xor, aes, aes-128, aes-192, blowfish, twofish, cast5, 3des, xtea, salsa20
|
||||
|
||||
Simple-Obfs scheme:
|
||||
simple-obfs://host:port[?type=TYPE&host=HOST&uri=URI&ua=UA]
|
||||
|
||||
Available types for simple-obfs:
|
||||
http, tls
|
||||
|
||||
DNS forwarding server:
|
||||
dns=:53
|
||||
dnsserver=8.8.8.8:53
|
||||
dnsserver=1.1.1.1:53
|
||||
dnsrecord=www.example.com/1.2.3.4
|
||||
dnsrecord=www.example.com/2606:2800:220:1:248:1893:25c8:1946
|
||||
|
||||
Available forward strategies:
|
||||
rr: Round Robin mode
|
||||
ha: High Availability mode
|
||||
lha: Latency based High Availability mode
|
||||
dh: Destination Hashing mode
|
||||
|
||||
Forwarder option scheme: FORWARD_URL#OPTIONS
|
||||
priority: set the priority of that forwarder, default:0
|
||||
interface: set local interface or ip address used to connect remote server
|
||||
-
|
||||
Examples:
|
||||
socks5://1.1.1.1:1080#priority=100
|
||||
vmess://[security:]uuid@host:port?alterID=num#priority=200
|
||||
vmess://[security:]uuid@host:port?alterID=num#priority=200&interface=192.168.1.99
|
||||
vmess://[security:]uuid@host:port?alterID=num#priority=200&interface=eth0
|
||||
|
||||
Config file format(see `glider.conf.example` as an example):
|
||||
# COMMENT LINE
|
||||
KEY=VALUE
|
||||
KEY=VALUE
|
||||
# KEY equals to command line flag name: listen forward strategy...
|
||||
|
||||
Examples:
|
||||
glider -config glider.conf
|
||||
-run glider with specified config file.
|
||||
|
||||
glider -config glider.conf -rulefile office.rule -rulefile home.rule
|
||||
-run glider with specified global config file and rule config files.
|
||||
|
||||
glider -listen :8443
|
||||
-listen on :8443, serve as http/socks5 proxy on the same port.
|
||||
|
||||
glider -listen ss://AEAD_CHACHA20_POLY1305:pass@:8443
|
||||
-listen on 0.0.0.0:8443 as a ss server.
|
||||
|
||||
glider -listen socks5://:1080 -verbose
|
||||
-listen on :1080 as a socks5 proxy server, in verbose mode.
|
||||
|
||||
glider -listen tls://:443?cert=crtFilePath&key=keyFilePath,http:// -verbose
|
||||
-listen on :443 as a https(http over tls) proxy server.
|
||||
|
||||
glider -listen http://:8080 -forward socks5://127.0.0.1:1080
|
||||
-listen on :8080 as a http proxy server, forward all requests via socks5 server.
|
||||
|
||||
glider -listen redir://:1081 -forward ss://method:pass@1.1.1.1:8443
|
||||
-listen on :1081 as a transparent redirect server, forward all requests via remote ss server.
|
||||
|
||||
glider -listen redir://:1081 -forward "ssr://method:pass@1.1.1.1:8444?protocol=a&protocol_param=b&obfs=c&obfs_param=d"
|
||||
-listen on :1081 as a transparent redirect server, forward all requests via remote ssr server.
|
||||
|
||||
glider -listen redir://:1081 -forward "tls://1.1.1.1:443,vmess://security:uuid@?alterID=10"
|
||||
-listen on :1081 as a transparent redirect server, forward all requests via remote tls+vmess server.
|
||||
|
||||
glider -listen redir://:1081 -forward "ws://1.1.1.1:80,vmess://security:uuid@?alterID=10"
|
||||
-listen on :1081 as a transparent redirect server, forward all requests via remote ws+vmess server.
|
||||
|
||||
glider -listen tcptun://:80=2.2.2.2:80 -forward ss://method:pass@1.1.1.1:8443
|
||||
-listen on :80 and forward all requests to 2.2.2.2:80 via remote ss server.
|
||||
|
||||
glider -listen udptun://:53=8.8.8.8:53 -forward ss://method:pass@1.1.1.1:8443
|
||||
-listen on :53 and forward all udp requests to 8.8.8.8:53 via remote ss server.
|
||||
|
||||
glider -listen uottun://:53=8.8.8.8:53 -forward ss://method:pass@1.1.1.1:8443
|
||||
-listen on :53 and forward all udp requests via udp over tcp tunnel.
|
||||
|
||||
glider -listen socks5://:1080 -listen http://:8080 -forward ss://method:pass@1.1.1.1:8443
|
||||
-listen on :1080 as socks5 server, :8080 as http proxy server, forward all requests via remote ss server.
|
||||
|
||||
glider -listen redir://:1081 -dns=:53 -dnsserver=8.8.8.8:53 -forward ss://method:pass@server1:port1,ss://method:pass@server2:port2
|
||||
-listen on :1081 as transparent redirect server, :53 as dns server, use forward chain: server1 -> server2.
|
||||
|
||||
glider -listen socks5://:1080 -forward ss://method:pass@server1:port1 -forward ss://method:pass@server2:port2 -strategy rr
|
||||
-listen on :1080 as socks5 server, forward requests via server1 and server2 in round robin mode.
|
||||
|
||||
glider -verbose -dns=:53 -dnsserver=8.8.8.8:53 -dnsrecord=www.example.com/1.2.3.4
|
||||
-listen on :53 as dns server, forward dns requests to 8.8.8.8:53, return 1.2.3.4 when resolving www.example.com.
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
- [ConfigFile](config)
|
||||
- [glider.conf.example](config/glider.conf.example)
|
||||
- [office.rule.example](config/rules.d/office.rule.example)
|
||||
- [Examples](config/examples)
|
||||
- [transparent proxy with dnsmasq](config/examples/8.transparent_proxy_with_dnsmasq)
|
||||
- [transparent proxy without dnsmasq](config/examples/9.transparent_proxy_without_dnsmasq)
|
||||
|
||||
### Proxy & Protocol Chain
|
||||
In glider, you can easily chain several proxy servers or protocols together, e.g:
|
||||
|
||||
- Chain proxy servers:
|
||||
|
||||
```bash
|
||||
forward=http://1.1.1.1:80,socks5://2.2.2.2:1080,ss://method:pass@3.3.3.3:8443@
|
||||
```
|
||||
|
||||
- Chain protocols: https proxy (http over tls)
|
||||
|
||||
```bash
|
||||
forward=tls://1.1.1.1:443,http://
|
||||
```
|
||||
|
||||
- Chain protocols: vmess over ws over tls
|
||||
|
||||
```bash
|
||||
forward=tls://1.1.1.1:443,ws://,vmess://5a146038-0b56-4e95-b1dc-5c6f5a32cd98@?alterID=2
|
||||
```
|
||||
|
||||
- Chain protocols and servers:
|
||||
|
||||
``` bash
|
||||
forward=socks5://1.1.1.1:1080,tls://2.2.2.2:443,ws://,vmess://5a146038-0b56-4e95-b1dc-5c6f5a32cd98@?alterID=2
|
||||
```
|
||||
|
||||
- Chain protocols in listener: https proxy server
|
||||
|
||||
``` bash
|
||||
listen=tls://:443?cert=crtFilePath&key=keyFilePath,http://
|
||||
```
|
||||
|
||||
|
||||
## Service
|
||||
|
||||
- systemd: [https://github.com/nadoo/glider/blob/master/systemd/](https://github.com/nadoo/glider/blob/master/systemd/)
|
||||
|
||||
## Links
|
||||
|
||||
- [conflag](https://github.com/nadoo/conflag): command line and config file parse support
|
||||
- [ArchLinux](https://www.archlinux.org/packages/community/x86_64/glider): a great linux distribution with glider pre-built package
|
90
common/conn/conn.go
Normal file
90
common/conn/conn.go
Normal file
@ -0,0 +1,90 @@
|
||||
package conn
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// UDPBufSize is the size of udp buffer.
|
||||
const UDPBufSize = 65536
|
||||
|
||||
// Conn is a base conn struct.
|
||||
type Conn struct {
|
||||
r *bufio.Reader
|
||||
net.Conn
|
||||
}
|
||||
|
||||
// NewConn returns a new conn.
|
||||
func NewConn(c net.Conn) *Conn {
|
||||
return &Conn{bufio.NewReader(c), c}
|
||||
}
|
||||
|
||||
// Peek returns the next n bytes without advancing the reader.
|
||||
func (c *Conn) Peek(n int) ([]byte, error) {
|
||||
return c.r.Peek(n)
|
||||
}
|
||||
|
||||
func (c *Conn) Read(p []byte) (int, error) {
|
||||
return c.r.Read(p)
|
||||
}
|
||||
|
||||
// Reader returns the internal bufio.Reader.
|
||||
func (c *Conn) Reader() *bufio.Reader {
|
||||
return c.r
|
||||
}
|
||||
|
||||
// Relay relays between left and right.
|
||||
func Relay(left, right net.Conn) (int64, int64, error) {
|
||||
type res struct {
|
||||
N int64
|
||||
Err error
|
||||
}
|
||||
ch := make(chan res)
|
||||
|
||||
go func() {
|
||||
n, err := io.Copy(right, left)
|
||||
right.SetDeadline(time.Now()) // wake up the other goroutine blocking on right
|
||||
left.SetDeadline(time.Now()) // wake up the other goroutine blocking on left
|
||||
ch <- res{n, err}
|
||||
}()
|
||||
|
||||
n, err := io.Copy(left, right)
|
||||
right.SetDeadline(time.Now()) // wake up the other goroutine blocking on right
|
||||
left.SetDeadline(time.Now()) // wake up the other goroutine blocking on left
|
||||
rs := <-ch
|
||||
|
||||
if err == nil {
|
||||
err = rs.Err
|
||||
}
|
||||
return n, rs.N, err
|
||||
}
|
||||
|
||||
// RelayUDP copys from src to dst at target with read timeout.
|
||||
func RelayUDP(dst net.PacketConn, target net.Addr, src net.PacketConn, timeout time.Duration) error {
|
||||
buf := make([]byte, UDPBufSize)
|
||||
for {
|
||||
src.SetReadDeadline(time.Now().Add(timeout))
|
||||
n, _, err := src.ReadFrom(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = dst.WriteTo(buf[:n], target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// OutboundIP returns preferred outbound ip of this machine.
|
||||
func OutboundIP() string {
|
||||
conn, err := net.Dial("udp", "8.8.8.8:80")
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
return conn.LocalAddr().(*net.UDPAddr).IP.String()
|
||||
}
|
19
common/log/log.go
Normal file
19
common/log/log.go
Normal file
@ -0,0 +1,19 @@
|
||||
package log
|
||||
|
||||
import stdlog "log"
|
||||
|
||||
// Func defines a simple log function
|
||||
type Func func(f string, v ...interface{})
|
||||
|
||||
// F is the main log function
|
||||
var F Func = func(string, ...interface{}) {}
|
||||
|
||||
// Fatal log and exit
|
||||
func Fatal(v ...interface{}) {
|
||||
stdlog.Fatal(v...)
|
||||
}
|
||||
|
||||
// Fatalf log and exit
|
||||
func Fatalf(f string, v ...interface{}) {
|
||||
stdlog.Fatalf(f, v...)
|
||||
}
|
177
common/socks/socks.go
Normal file
177
common/socks/socks.go
Normal file
@ -0,0 +1,177 @@
|
||||
package socks
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// SOCKS auth type
|
||||
const (
|
||||
AuthNone = 0
|
||||
AuthPassword = 2
|
||||
)
|
||||
|
||||
// SOCKS request commands as defined in RFC 1928 section 4.
|
||||
const (
|
||||
CmdConnect = 1
|
||||
CmdBind = 2
|
||||
CmdUDPAssociate = 3
|
||||
)
|
||||
|
||||
// SOCKS address types as defined in RFC 1928 section 5.
|
||||
const (
|
||||
ATypIP4 = 1
|
||||
ATypDomain = 3
|
||||
ATypIP6 = 4
|
||||
)
|
||||
|
||||
// MaxAddrLen is the maximum size of SOCKS address in bytes.
|
||||
const MaxAddrLen = 1 + 1 + 255 + 2
|
||||
|
||||
// Errors are socks5 errors
|
||||
var Errors = []error{
|
||||
errors.New(""),
|
||||
errors.New("general failure"),
|
||||
errors.New("connection forbidden"),
|
||||
errors.New("network unreachable"),
|
||||
errors.New("host unreachable"),
|
||||
errors.New("connection refused"),
|
||||
errors.New("TTL expired"),
|
||||
errors.New("command not supported"),
|
||||
errors.New("address type not supported"),
|
||||
errors.New("socks5UDPAssociate"),
|
||||
}
|
||||
|
||||
// Addr .
|
||||
type Addr []byte
|
||||
|
||||
// String serializes SOCKS address a to string form.
|
||||
func (a Addr) String() string {
|
||||
var host, port string
|
||||
|
||||
switch ATYP(a[0]) { // address type
|
||||
case ATypDomain:
|
||||
host = string(a[2 : 2+int(a[1])])
|
||||
port = strconv.Itoa((int(a[2+int(a[1])]) << 8) | int(a[2+int(a[1])+1]))
|
||||
case ATypIP4:
|
||||
host = net.IP(a[1 : 1+net.IPv4len]).String()
|
||||
port = strconv.Itoa((int(a[1+net.IPv4len]) << 8) | int(a[1+net.IPv4len+1]))
|
||||
case ATypIP6:
|
||||
host = net.IP(a[1 : 1+net.IPv6len]).String()
|
||||
port = strconv.Itoa((int(a[1+net.IPv6len]) << 8) | int(a[1+net.IPv6len+1]))
|
||||
}
|
||||
|
||||
return net.JoinHostPort(host, port)
|
||||
}
|
||||
|
||||
// UoT returns whether it is udp over tcp
|
||||
func UoT(b byte) bool {
|
||||
return b&0x8 == 0x8
|
||||
}
|
||||
|
||||
// ATYP returns the address type
|
||||
func ATYP(b byte) int {
|
||||
return int(b &^ 0x8)
|
||||
}
|
||||
|
||||
// ReadAddrBuf reads just enough bytes from r to get a valid Addr.
|
||||
func ReadAddrBuf(r io.Reader, b []byte) (Addr, error) {
|
||||
if len(b) < MaxAddrLen {
|
||||
return nil, io.ErrShortBuffer
|
||||
}
|
||||
_, err := io.ReadFull(r, b[:1]) // read 1st byte for address type
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch ATYP(b[0]) {
|
||||
case ATypDomain:
|
||||
_, err = io.ReadFull(r, b[1:2]) // read 2nd byte for domain length
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = io.ReadFull(r, b[2:2+int(b[1])+2])
|
||||
return b[:1+1+int(b[1])+2], err
|
||||
case ATypIP4:
|
||||
_, err = io.ReadFull(r, b[1:1+net.IPv4len+2])
|
||||
return b[:1+net.IPv4len+2], err
|
||||
case ATypIP6:
|
||||
_, err = io.ReadFull(r, b[1:1+net.IPv6len+2])
|
||||
return b[:1+net.IPv6len+2], err
|
||||
}
|
||||
|
||||
return nil, Errors[8]
|
||||
}
|
||||
|
||||
// ReadAddr reads just enough bytes from r to get a valid Addr.
|
||||
func ReadAddr(r io.Reader) (Addr, error) {
|
||||
return ReadAddrBuf(r, make([]byte, MaxAddrLen))
|
||||
}
|
||||
|
||||
// SplitAddr slices a SOCKS address from beginning of b. Returns nil if failed.
|
||||
func SplitAddr(b []byte) Addr {
|
||||
addrLen := 1
|
||||
if len(b) < addrLen {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch ATYP(b[0]) {
|
||||
case ATypDomain:
|
||||
if len(b) < 2 {
|
||||
return nil
|
||||
}
|
||||
addrLen = 1 + 1 + int(b[1]) + 2
|
||||
case ATypIP4:
|
||||
addrLen = 1 + net.IPv4len + 2
|
||||
case ATypIP6:
|
||||
addrLen = 1 + net.IPv6len + 2
|
||||
default:
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
if len(b) < addrLen {
|
||||
return nil
|
||||
}
|
||||
|
||||
return b[:addrLen]
|
||||
}
|
||||
|
||||
// ParseAddr parses the address in string s. Returns nil if failed.
|
||||
func ParseAddr(s string) Addr {
|
||||
var addr Addr
|
||||
host, port, err := net.SplitHostPort(s)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if ip := net.ParseIP(host); ip != nil {
|
||||
if ip4 := ip.To4(); ip4 != nil {
|
||||
addr = make([]byte, 1+net.IPv4len+2)
|
||||
addr[0] = ATypIP4
|
||||
copy(addr[1:], ip4)
|
||||
} else {
|
||||
addr = make([]byte, 1+net.IPv6len+2)
|
||||
addr[0] = ATypIP6
|
||||
copy(addr[1:], ip)
|
||||
}
|
||||
} else {
|
||||
if len(host) > 255 {
|
||||
return nil
|
||||
}
|
||||
addr = make([]byte, 1+1+len(host)+2)
|
||||
addr[0] = ATypDomain
|
||||
addr[1] = byte(len(host))
|
||||
copy(addr[2:], host)
|
||||
}
|
||||
|
||||
portnum, err := strconv.ParseUint(port, 10, 16)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
addr[len(addr)-2], addr[len(addr)-1] = byte(portnum>>8), byte(portnum)
|
||||
|
||||
return addr
|
||||
}
|
308
conf.go
Normal file
308
conf.go
Normal file
@ -0,0 +1,308 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/nadoo/conflag"
|
||||
|
||||
"github.com/nadoo/glider/dns"
|
||||
"github.com/nadoo/glider/rule"
|
||||
"github.com/nadoo/glider/strategy"
|
||||
)
|
||||
|
||||
var flag = conflag.New()
|
||||
|
||||
var conf struct {
|
||||
Verbose bool
|
||||
|
||||
Listen []string
|
||||
|
||||
Forward []string
|
||||
StrategyConfig strategy.Config
|
||||
|
||||
RuleFile []string
|
||||
RulesDir string
|
||||
|
||||
DNS string
|
||||
DNSConfig dns.Config
|
||||
|
||||
rules []*rule.Config
|
||||
}
|
||||
|
||||
func confInit() {
|
||||
flag.BoolVar(&conf.Verbose, "verbose", false, "verbose mode")
|
||||
flag.StringSliceUniqVar(&conf.Listen, "listen", nil, "listen url, format: SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS")
|
||||
|
||||
flag.StringSliceUniqVar(&conf.Forward, "forward", nil, "forward url, format: SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS[,SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS]")
|
||||
flag.StringVar(&conf.StrategyConfig.Strategy, "strategy", "rr", "forward strategy, default: rr")
|
||||
flag.StringVar(&conf.StrategyConfig.CheckWebSite, "checkwebsite", "www.apple.com", "proxy check HTTP(NOT HTTPS) website address, format: HOST[:PORT], default port: 80")
|
||||
flag.IntVar(&conf.StrategyConfig.CheckInterval, "checkinterval", 30, "proxy check interval(seconds)")
|
||||
flag.IntVar(&conf.StrategyConfig.CheckTimeout, "checktimeout", 10, "proxy check timeout(seconds)")
|
||||
flag.IntVar(&conf.StrategyConfig.MaxFailures, "maxfailures", 3, "max failures to change forwarder status to disabled")
|
||||
flag.StringVar(&conf.StrategyConfig.IntFace, "interface", "", "source ip or source interface")
|
||||
|
||||
flag.StringSliceUniqVar(&conf.RuleFile, "rulefile", nil, "rule file path")
|
||||
flag.StringVar(&conf.RulesDir, "rules-dir", "", "rule file folder")
|
||||
|
||||
flag.StringVar(&conf.DNS, "dns", "", "local dns server listen address")
|
||||
flag.StringSliceUniqVar(&conf.DNSConfig.Servers, "dnsserver", []string{"8.8.8.8:53"}, "remote dns server address")
|
||||
flag.BoolVar(&conf.DNSConfig.AlwaysTCP, "dnsalwaystcp", false, "always use tcp to query upstream dns servers no matter there is a forwarder or not")
|
||||
flag.IntVar(&conf.DNSConfig.Timeout, "dnstimeout", 3, "timeout value used in multiple dnsservers switch(seconds)")
|
||||
flag.IntVar(&conf.DNSConfig.MaxTTL, "dnsmaxttl", 1800, "maximum TTL value for entries in the CACHE(seconds)")
|
||||
flag.IntVar(&conf.DNSConfig.MinTTL, "dnsminttl", 0, "minimum TTL value for entries in the CACHE(seconds)")
|
||||
flag.StringSliceUniqVar(&conf.DNSConfig.Records, "dnsrecord", nil, "custom dns record, format: domain/ip")
|
||||
|
||||
flag.Usage = usage
|
||||
err := flag.Parse()
|
||||
if err != nil {
|
||||
// flag.Usage()
|
||||
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
if len(conf.Listen) == 0 && conf.DNS == "" {
|
||||
// flag.Usage()
|
||||
fmt.Fprintf(os.Stderr, "ERROR: listen url must be specified.\n")
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
// rulefiles
|
||||
for _, ruleFile := range conf.RuleFile {
|
||||
if !path.IsAbs(ruleFile) {
|
||||
ruleFile = path.Join(flag.ConfDir(), ruleFile)
|
||||
}
|
||||
|
||||
rule, err := rule.NewConfFromFile(ruleFile)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
conf.rules = append(conf.rules, rule)
|
||||
}
|
||||
|
||||
if conf.RulesDir != "" {
|
||||
if !path.IsAbs(conf.RulesDir) {
|
||||
conf.RulesDir = path.Join(flag.ConfDir(), conf.RulesDir)
|
||||
}
|
||||
|
||||
ruleFolderFiles, _ := rule.ListDir(conf.RulesDir, ".rule")
|
||||
for _, ruleFile := range ruleFolderFiles {
|
||||
rule, err := rule.NewConfFromFile(ruleFile)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
conf.rules = append(conf.rules, rule)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func usage() {
|
||||
app := os.Args[0]
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
fmt.Fprintf(os.Stderr, "%s %s usage:\n", app, version)
|
||||
flag.PrintDefaults()
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Available Schemes:\n")
|
||||
fmt.Fprintf(os.Stderr, " mixed: serve as a http/socks5 proxy on the same port. (default)\n")
|
||||
fmt.Fprintf(os.Stderr, " ss: ss proxy\n")
|
||||
fmt.Fprintf(os.Stderr, " socks5: socks5 proxy\n")
|
||||
fmt.Fprintf(os.Stderr, " http: http proxy\n")
|
||||
fmt.Fprintf(os.Stderr, " ssr: ssr proxy\n")
|
||||
fmt.Fprintf(os.Stderr, " vmess: vmess proxy\n")
|
||||
fmt.Fprintf(os.Stderr, " tls: tls transport\n")
|
||||
fmt.Fprintf(os.Stderr, " ws: websocket transport\n")
|
||||
fmt.Fprintf(os.Stderr, " redir: redirect proxy. (used on linux as a transparent proxy with iptables redirect rules)\n")
|
||||
fmt.Fprintf(os.Stderr, " redir6: redirect proxy(ipv6)\n")
|
||||
fmt.Fprintf(os.Stderr, " tcptun: tcp tunnel\n")
|
||||
fmt.Fprintf(os.Stderr, " udptun: udp tunnel\n")
|
||||
fmt.Fprintf(os.Stderr, " uottun: udp over tcp tunnel\n")
|
||||
fmt.Fprintf(os.Stderr, " unix: unix domain socket\n")
|
||||
fmt.Fprintf(os.Stderr, " kcp: kcp protocol\n")
|
||||
fmt.Fprintf(os.Stderr, " simple-obfs: simple-obfs protocol\n")
|
||||
fmt.Fprintf(os.Stderr, " reject: a virtual proxy which just reject connections\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Available schemes for different modes:\n")
|
||||
fmt.Fprintf(os.Stderr, " listen: mixed ss socks5 http redir redir6 tcptun udptun uottun tls unix kcp\n")
|
||||
fmt.Fprintf(os.Stderr, " forward: reject ss socks5 http ssr vmess tls ws unix kcp simple-obfs\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "SS scheme:\n")
|
||||
fmt.Fprintf(os.Stderr, " ss://method:pass@host:port\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Available methods for ss:\n")
|
||||
fmt.Fprintf(os.Stderr, " AEAD Ciphers:\n")
|
||||
fmt.Fprintf(os.Stderr, " AEAD_AES_128_GCM AEAD_AES_192_GCM AEAD_AES_256_GCM AEAD_CHACHA20_POLY1305 AEAD_XCHACHA20_POLY1305\n")
|
||||
fmt.Fprintf(os.Stderr, " Stream Ciphers:\n")
|
||||
fmt.Fprintf(os.Stderr, " AES-128-CFB AES-128-CTR AES-192-CFB AES-192-CTR AES-256-CFB AES-256-CTR CHACHA20-IETF XCHACHA20 CHACHA20 RC4-MD5\n")
|
||||
fmt.Fprintf(os.Stderr, " Alias:\n")
|
||||
fmt.Fprintf(os.Stderr, " chacha20-ietf-poly1305 = AEAD_CHACHA20_POLY1305, xchacha20-ietf-poly1305 = AEAD_XCHACHA20_POLY1305\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "SSR scheme:\n")
|
||||
fmt.Fprintf(os.Stderr, " ssr://method:pass@host:port?protocol=xxx&protocol_param=yyy&obfs=zzz&obfs_param=xyz\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "VMess scheme:\n")
|
||||
fmt.Fprintf(os.Stderr, " vmess://[security:]uuid@host:port?alterID=num\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Available securities for vmess:\n")
|
||||
fmt.Fprintf(os.Stderr, " none, aes-128-gcm, chacha20-poly1305\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "TLS client scheme:\n")
|
||||
fmt.Fprintf(os.Stderr, " tls://host:port[?skipVerify=true]\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Proxy over tls client:\n")
|
||||
fmt.Fprintf(os.Stderr, " tls://host:port[?skipVerify=true],scheme://\n")
|
||||
fmt.Fprintf(os.Stderr, " tls://host:port[?skipVerify=true],http://[user:pass@]\n")
|
||||
fmt.Fprintf(os.Stderr, " tls://host:port[?skipVerify=true],socks5://[user:pass@]\n")
|
||||
fmt.Fprintf(os.Stderr, " tls://host:port[?skipVerify=true],vmess://[security:]uuid@?alterID=num\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "TLS server scheme:\n")
|
||||
fmt.Fprintf(os.Stderr, " tls://host:port?cert=PATH&key=PATH\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Proxy over tls server:\n")
|
||||
fmt.Fprintf(os.Stderr, " tls://host:port?cert=PATH&key=PATH,scheme://\n")
|
||||
fmt.Fprintf(os.Stderr, " tls://host:port?cert=PATH&key=PATH,http://\n")
|
||||
fmt.Fprintf(os.Stderr, " tls://host:port?cert=PATH&key=PATH,socks5://\n")
|
||||
fmt.Fprintf(os.Stderr, " tls://host:port?cert=PATH&key=PATH,ss://method:pass@\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Websocket scheme:\n")
|
||||
fmt.Fprintf(os.Stderr, " ws://host:port[/path]\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Websocket with a specified proxy protocol:\n")
|
||||
fmt.Fprintf(os.Stderr, " ws://host:port[/path],scheme://\n")
|
||||
fmt.Fprintf(os.Stderr, " ws://host:port[/path],http://[user:pass@]\n")
|
||||
fmt.Fprintf(os.Stderr, " ws://host:port[/path],socks5://[user:pass@]\n")
|
||||
fmt.Fprintf(os.Stderr, " ws://host:port[/path],vmess://[security:]uuid@?alterID=num\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "TLS and Websocket with a specified proxy protocol:\n")
|
||||
fmt.Fprintf(os.Stderr, " tls://host:port[?skipVerify=true],ws://[@/path],scheme://\n")
|
||||
fmt.Fprintf(os.Stderr, " tls://host:port[?skipVerify=true],ws://[@/path],http://[user:pass@]\n")
|
||||
fmt.Fprintf(os.Stderr, " tls://host:port[?skipVerify=true],ws://[@/path],socks5://[user:pass@]\n")
|
||||
fmt.Fprintf(os.Stderr, " tls://host:port[?skipVerify=true],ws://[@/path],vmess://[security:]uuid@?alterID=num\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Unix domain socket scheme:\n")
|
||||
fmt.Fprintf(os.Stderr, " unix://path\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "KCP scheme:\n")
|
||||
fmt.Fprintf(os.Stderr, " kcp://CRYPT:KEY@host:port[?dataShards=NUM&parityShards=NUM]\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Available crypt types for KCP:\n")
|
||||
fmt.Fprintf(os.Stderr, " none, sm4, tea, xor, aes, aes-128, aes-192, blowfish, twofish, cast5, 3des, xtea, salsa20\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Simple-Obfs scheme:\n")
|
||||
fmt.Fprintf(os.Stderr, " simple-obfs://host:port[?type=TYPE&host=HOST&uri=URI&ua=UA]\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Available types for simple-obfs:\n")
|
||||
fmt.Fprintf(os.Stderr, " http, tls\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "DNS forwarding server:\n")
|
||||
fmt.Fprintf(os.Stderr, " dns=:53\n")
|
||||
fmt.Fprintf(os.Stderr, " dnsserver=8.8.8.8:53\n")
|
||||
fmt.Fprintf(os.Stderr, " dnsserver=1.1.1.1:53\n")
|
||||
fmt.Fprintf(os.Stderr, " dnsrecord=www.example.com/1.2.3.4\n")
|
||||
fmt.Fprintf(os.Stderr, " dnsrecord=www.example.com/2606:2800:220:1:248:1893:25c8:1946\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Available forward strategies:\n")
|
||||
fmt.Fprintf(os.Stderr, " rr: Round Robin mode\n")
|
||||
fmt.Fprintf(os.Stderr, " ha: High Availability mode\n")
|
||||
fmt.Fprintf(os.Stderr, " lha: Latency based High Availability mode\n")
|
||||
fmt.Fprintf(os.Stderr, " dh: Destination Hashing mode\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Forwarder option scheme: FORWARD_URL#OPTIONS\n")
|
||||
fmt.Fprintf(os.Stderr, " priority: set the priority of that forwarder, default:0\n")
|
||||
fmt.Fprintf(os.Stderr, " interface: set local interface or ip address used to connect remote server\n")
|
||||
fmt.Fprintf(os.Stderr, " -\n")
|
||||
fmt.Fprintf(os.Stderr, " Examples:\n")
|
||||
fmt.Fprintf(os.Stderr, " socks5://1.1.1.1:1080#priority=100\n")
|
||||
fmt.Fprintf(os.Stderr, " vmess://[security:]uuid@host:port?alterID=num#priority=200\n")
|
||||
fmt.Fprintf(os.Stderr, " vmess://[security:]uuid@host:port?alterID=num#priority=200&interface=192.168.1.99\n")
|
||||
fmt.Fprintf(os.Stderr, " vmess://[security:]uuid@host:port?alterID=num#priority=200&interface=eth0\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Config file format(see `"+app+".conf.example` as an example):\n")
|
||||
fmt.Fprintf(os.Stderr, " # COMMENT LINE\n")
|
||||
fmt.Fprintf(os.Stderr, " KEY=VALUE\n")
|
||||
fmt.Fprintf(os.Stderr, " KEY=VALUE\n")
|
||||
fmt.Fprintf(os.Stderr, " # KEY equals to command line flag name: listen forward strategy...\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Examples:\n")
|
||||
fmt.Fprintf(os.Stderr, " "+app+" -config glider.conf\n")
|
||||
fmt.Fprintf(os.Stderr, " -run glider with specified config file.\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
fmt.Fprintf(os.Stderr, " "+app+" -config glider.conf -rulefile office.rule -rulefile home.rule\n")
|
||||
fmt.Fprintf(os.Stderr, " -run glider with specified global config file and rule config files.\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
fmt.Fprintf(os.Stderr, " "+app+" -listen :8443\n")
|
||||
fmt.Fprintf(os.Stderr, " -listen on :8443, serve as http/socks5 proxy on the same port.\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
fmt.Fprintf(os.Stderr, " "+app+" -listen ss://AEAD_CHACHA20_POLY1305:pass@:8443\n")
|
||||
fmt.Fprintf(os.Stderr, " -listen on 0.0.0.0:8443 as a ss server.\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
fmt.Fprintf(os.Stderr, " "+app+" -listen socks5://:1080 -verbose\n")
|
||||
fmt.Fprintf(os.Stderr, " -listen on :1080 as a socks5 proxy server, in verbose mode.\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
fmt.Fprintf(os.Stderr, " "+app+" -listen tls://:443?cert=crtFilePath&key=keyFilePath,http:// -verbose\n")
|
||||
fmt.Fprintf(os.Stderr, " -listen on :443 as a https(http over tls) proxy server.\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
fmt.Fprintf(os.Stderr, " "+app+" -listen http://:8080 -forward socks5://127.0.0.1:1080\n")
|
||||
fmt.Fprintf(os.Stderr, " -listen on :8080 as a http proxy server, forward all requests via socks5 server.\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
fmt.Fprintf(os.Stderr, " "+app+" -listen redir://:1081 -forward ss://method:pass@1.1.1.1:8443\n")
|
||||
fmt.Fprintf(os.Stderr, " -listen on :1081 as a transparent redirect server, forward all requests via remote ss server.\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
fmt.Fprintf(os.Stderr, " "+app+" -listen redir://:1081 -forward \"ssr://method:pass@1.1.1.1:8444?protocol=a&protocol_param=b&obfs=c&obfs_param=d\"\n")
|
||||
fmt.Fprintf(os.Stderr, " -listen on :1081 as a transparent redirect server, forward all requests via remote ssr server.\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
fmt.Fprintf(os.Stderr, " "+app+" -listen redir://:1081 -forward \"tls://1.1.1.1:443,vmess://security:uuid@?alterID=10\"\n")
|
||||
fmt.Fprintf(os.Stderr, " -listen on :1081 as a transparent redirect server, forward all requests via remote tls+vmess server.\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
fmt.Fprintf(os.Stderr, " "+app+" -listen redir://:1081 -forward \"ws://1.1.1.1:80,vmess://security:uuid@?alterID=10\"\n")
|
||||
fmt.Fprintf(os.Stderr, " -listen on :1081 as a transparent redirect server, forward all requests via remote ws+vmess server.\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
fmt.Fprintf(os.Stderr, " "+app+" -listen tcptun://:80=2.2.2.2:80 -forward ss://method:pass@1.1.1.1:8443\n")
|
||||
fmt.Fprintf(os.Stderr, " -listen on :80 and forward all requests to 2.2.2.2:80 via remote ss server.\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
fmt.Fprintf(os.Stderr, " "+app+" -listen udptun://:53=8.8.8.8:53 -forward ss://method:pass@1.1.1.1:8443\n")
|
||||
fmt.Fprintf(os.Stderr, " -listen on :53 and forward all udp requests to 8.8.8.8:53 via remote ss server.\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
fmt.Fprintf(os.Stderr, " "+app+" -listen uottun://:53=8.8.8.8:53 -forward ss://method:pass@1.1.1.1:8443\n")
|
||||
fmt.Fprintf(os.Stderr, " -listen on :53 and forward all udp requests via udp over tcp tunnel.\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
fmt.Fprintf(os.Stderr, " "+app+" -listen socks5://:1080 -listen http://:8080 -forward ss://method:pass@1.1.1.1:8443\n")
|
||||
fmt.Fprintf(os.Stderr, " -listen on :1080 as socks5 server, :8080 as http proxy server, forward all requests via remote ss server.\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
fmt.Fprintf(os.Stderr, " "+app+" -listen redir://:1081 -dns=:53 -dnsserver=8.8.8.8:53 -forward ss://method:pass@server1:port1,ss://method:pass@server2:port2\n")
|
||||
fmt.Fprintf(os.Stderr, " -listen on :1081 as transparent redirect server, :53 as dns server, use forward chain: server1 -> server2.\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
fmt.Fprintf(os.Stderr, " "+app+" -listen socks5://:1080 -forward ss://method:pass@server1:port1 -forward ss://method:pass@server2:port2 -strategy rr\n")
|
||||
fmt.Fprintf(os.Stderr, " -listen on :1080 as socks5 server, forward requests via server1 and server2 in round robin mode.\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
fmt.Fprintf(os.Stderr, " "+app+" -verbose -dns=:53 -dnsserver=8.8.8.8:53 -dnsrecord=www.example.com/1.2.3.4\n")
|
||||
fmt.Fprintf(os.Stderr, " -listen on :53 as dns server, forward dns requests to 8.8.8.8:53, return 1.2.3.4 when resolving www.example.com.\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
}
|
94
config/README.md
Normal file
94
config/README.md
Normal file
@ -0,0 +1,94 @@
|
||||
|
||||
## Config File
|
||||
Command:
|
||||
```bash
|
||||
glider -config glider.conf
|
||||
```
|
||||
Config file, **just use the command line flag name as the key name**:
|
||||
```bash
|
||||
### glider config file
|
||||
|
||||
# verbose mode, print logs
|
||||
verbose
|
||||
|
||||
# listen on 8443, serve as http/socks5 proxy on the same port.
|
||||
listen=:8443
|
||||
|
||||
# upstream forward proxy
|
||||
forward=socks5://192.168.1.10:1080
|
||||
|
||||
# upstream forward proxy
|
||||
forward=ss://method:pass@1.1.1.1:8443
|
||||
|
||||
# upstream forward proxy (forward chain)
|
||||
forward=http://1.1.1.1:8080,socks5://2.2.2.2:1080
|
||||
|
||||
# multiple upstream proxies forwad strategy
|
||||
strategy=rr
|
||||
|
||||
# Used to connect via forwarders, if the host is unreachable, the forwarder
|
||||
# will be set to disabled.
|
||||
# MUST be a HTTP website server address, format: HOST[:PORT]. HTTPS NOT SUPPORTED.
|
||||
checkwebsite=www.apple.com
|
||||
|
||||
# check interval
|
||||
checkinterval=30
|
||||
|
||||
|
||||
# Setup a dns forwarding server
|
||||
dns=:53
|
||||
# global remote dns server (you can specify different dns server in rule file)
|
||||
dnsserver=8.8.8.8:53
|
||||
|
||||
# RULE FILES
|
||||
rules-dir=rules.d
|
||||
#rulefile=office.rule
|
||||
#rulefile=home.rule
|
||||
|
||||
# INCLUDE MORE CONFIG FILES
|
||||
#include=dnsrecord.inc.conf
|
||||
#include=more.inc.conf
|
||||
```
|
||||
See:
|
||||
- [glider.conf.example](config/glider.conf.example)
|
||||
- [examples](config/examples)
|
||||
|
||||
## Rule File
|
||||
Rule file, **same as the config file but specify forwarders based on destinations**:
|
||||
```bash
|
||||
# YOU CAN USE ALL KEYS IN THE GLOBAL CONFIG FILE EXCEPT "listen", "rulefile"
|
||||
forward=socks5://192.168.1.10:1080
|
||||
forward=ss://method:pass@1.1.1.1:8443
|
||||
forward=http://192.168.2.1:8080,socks5://192.168.2.2:1080
|
||||
strategy=rr
|
||||
checkwebsite=www.apple.com
|
||||
checkinterval=30
|
||||
|
||||
# DNS SERVER for domains in this rule file
|
||||
dnsserver=208.67.222.222:53
|
||||
|
||||
# IPSET MANAGEMENT
|
||||
# ----------------
|
||||
# Create and mange ipset on linux based on destinations in rule files
|
||||
# - add ip/cidrs in rule files on startup
|
||||
# - add resolved ips for domains in rule files by dns forwarding server
|
||||
# Usually used in transparent proxy mode on linux
|
||||
ipset=glider
|
||||
|
||||
# YOU CAN SPECIFY DESTINATIONS TO USE THE ABOVE FORWARDERS
|
||||
# matches abc.com and *.abc.com
|
||||
domain=abc.com
|
||||
|
||||
# matches 1.1.1.1
|
||||
ip=1.1.1.1
|
||||
|
||||
# matches 192.168.100.0/24
|
||||
cidr=192.168.100.0/24
|
||||
|
||||
# we can include a list file with only destinations settings
|
||||
include=office.list.example
|
||||
|
||||
```
|
||||
See:
|
||||
- [office.rule.example](rules.d/office.rule.example)
|
||||
- [examples](examples)
|
7
config/dnsrecord.inc.conf.example
Normal file
7
config/dnsrecord.inc.conf.example
Normal file
@ -0,0 +1,7 @@
|
||||
|
||||
# intranet
|
||||
dnsrecord=oa.yourcompany.local/10.0.0.1
|
||||
dnsrecord=git.yourcompany.local/10.0.0.2
|
||||
|
||||
# ad
|
||||
#dnsrecord=ad.domain/127.0.0.1
|
5
config/examples/1.simple_proxy_service/glider.conf
Normal file
5
config/examples/1.simple_proxy_service/glider.conf
Normal file
@ -0,0 +1,5 @@
|
||||
|
||||
# Verbose mode, print logs
|
||||
verbose=True
|
||||
|
||||
listen=:8443
|
7
config/examples/2.one_forwarder/glider.conf
Normal file
7
config/examples/2.one_forwarder/glider.conf
Normal file
@ -0,0 +1,7 @@
|
||||
|
||||
# Verbose mode, print logs
|
||||
verbose=True
|
||||
|
||||
listen=:8443
|
||||
|
||||
forward=socks5://192.168.1.10:1080
|
8
config/examples/3.forward_chain/glider.conf
Normal file
8
config/examples/3.forward_chain/glider.conf
Normal file
@ -0,0 +1,8 @@
|
||||
|
||||
# Verbose mode, print logs
|
||||
verbose=True
|
||||
|
||||
listen=:8443
|
||||
|
||||
# first connect forwarder1 then forwarder2 then internet
|
||||
forward=http://forwarder1:8080,socks5://forwarder2:1080
|
21
config/examples/4.multiple_forwarders/glider.conf
Normal file
21
config/examples/4.multiple_forwarders/glider.conf
Normal file
@ -0,0 +1,21 @@
|
||||
|
||||
# Verbose mode, print logs
|
||||
verbose=True
|
||||
|
||||
listen=:8443
|
||||
|
||||
# first connect forwarder1 then forwarder2 then internet
|
||||
forward=http://forwarder1:8080,socks5://forwarder2:1080
|
||||
forward=http://1.1.1.1:8080
|
||||
|
||||
# Round Robin mode: rr
|
||||
# High Availability mode: ha
|
||||
strategy=rr
|
||||
|
||||
# Used to connect via forwarders, if the host is unreachable, the forwarder
|
||||
# will be set to disabled.
|
||||
# MUST be a HTTP website server address, format: HOST[:PORT]. HTTPS NOT SUPPORTED.
|
||||
checkwebsite=www.apple.com
|
||||
|
||||
# check interval(seconds)
|
||||
checkinterval=30
|
9
config/examples/5.rule_default_direct/glider.conf
Normal file
9
config/examples/5.rule_default_direct/glider.conf
Normal file
@ -0,0 +1,9 @@
|
||||
|
||||
# Verbose mode, print logs
|
||||
verbose=True
|
||||
|
||||
listen=:8443
|
||||
|
||||
# NOTE HERE:
|
||||
# Specify a rule file
|
||||
rulefile=office.rule
|
29
config/examples/5.rule_default_direct/office.rule
Normal file
29
config/examples/5.rule_default_direct/office.rule
Normal file
@ -0,0 +1,29 @@
|
||||
|
||||
|
||||
# first connect forwarder1 then forwarder2 then internet
|
||||
forward=http://forwarder1:8080,socks5://forwarder2:1080
|
||||
forward=http://1.1.1.1:8080
|
||||
|
||||
# Round Robin mode: rr
|
||||
# High Availability mode: ha
|
||||
strategy=rr
|
||||
|
||||
checkwebsite=www.apple.com
|
||||
checkinterval=30
|
||||
|
||||
|
||||
# matches abc.com and *.abc.com
|
||||
domain=abc.com
|
||||
|
||||
# matches 1.1.1.1
|
||||
ip=1.1.1.1
|
||||
|
||||
# matches 192.168.100.0/24
|
||||
cidr=192.168.100.0/24
|
||||
|
||||
domain=example1.com
|
||||
domain=example2.com
|
||||
domain=example3.com
|
||||
ip=2.2.2.2
|
||||
ip=3.3.3.3
|
||||
cidr=172.16.0.0/24
|
8
config/examples/6.rule_default_forwarder/bypass.rule
Normal file
8
config/examples/6.rule_default_forwarder/bypass.rule
Normal file
@ -0,0 +1,8 @@
|
||||
|
||||
# matches abc.com and *.abc.com
|
||||
domain=abc.com
|
||||
|
||||
ip=127.0.0.1
|
||||
cidr=192.168.0.0/24
|
||||
cidr=192.168.1.0/24
|
||||
cidr=172.16.0.0/24
|
22
config/examples/6.rule_default_forwarder/glider.conf
Normal file
22
config/examples/6.rule_default_forwarder/glider.conf
Normal file
@ -0,0 +1,22 @@
|
||||
|
||||
|
||||
# Verbose mode, print logs
|
||||
verbose=True
|
||||
|
||||
listen=:8443
|
||||
|
||||
# first connect forwarder1 then forwarder2 then internet
|
||||
forward=http://forwarder1:8080,socks5://forwarder2:1080
|
||||
forward=http://1.1.1.1:8080
|
||||
|
||||
# Round Robin mode: rr
|
||||
# High Availability mode: ha
|
||||
strategy=rr
|
||||
|
||||
checkwebsite=www.apple.com
|
||||
checkinterval=30
|
||||
|
||||
|
||||
# NOTE HERE:
|
||||
# Specify a rule file
|
||||
rulefile=bypass.rule
|
8
config/examples/7.rule_multiple_rule_files/glider.conf
Normal file
8
config/examples/7.rule_multiple_rule_files/glider.conf
Normal file
@ -0,0 +1,8 @@
|
||||
|
||||
# Verbose mode, print logs
|
||||
verbose=True
|
||||
|
||||
listen=:8443
|
||||
|
||||
# parse all *.rule files in rules.d folder
|
||||
rules-dir=rules.d
|
18
config/examples/7.rule_multiple_rule_files/rules.d/home.rule
Normal file
18
config/examples/7.rule_multiple_rule_files/rules.d/home.rule
Normal file
@ -0,0 +1,18 @@
|
||||
|
||||
|
||||
forward=http://forwarder4:8080
|
||||
|
||||
# first connect forwarder1 then forwarder2 then internet
|
||||
forward=http://forwarder5:8080,socks6://forwarder3:1080
|
||||
|
||||
|
||||
# Round Robin mode: rr
|
||||
# High Availability mode: ha
|
||||
strategy=rr
|
||||
|
||||
checkwebsite=www.apple.com
|
||||
checkinterval=30
|
||||
|
||||
|
||||
# matches 192.168.0.0/16
|
||||
cidr=192.168.0.0/16
|
@ -0,0 +1,18 @@
|
||||
|
||||
|
||||
forward=http://forwarder1:8080
|
||||
|
||||
# first connect forwarder2 then forwarder3 then internet
|
||||
forward=http://forwarder2:8080,socks5://forwarder3:1080
|
||||
|
||||
|
||||
# Round Robin mode: rr
|
||||
# High Availability mode: ha
|
||||
strategy=rr
|
||||
|
||||
checkwebsite=www.apple.com
|
||||
checkinterval=30
|
||||
|
||||
|
||||
# matches 172.16.0.0/24
|
||||
cidr=172.16.0.0/24
|
44
config/examples/8.transparent_proxy_with_dnsmasq/README.md
Normal file
44
config/examples/8.transparent_proxy_with_dnsmasq/README.md
Normal file
@ -0,0 +1,44 @@
|
||||
|
||||
## 8. Transparent Proxy with dnsmasq
|
||||
|
||||
#### Setup a redirect proxy and a dns server with glider
|
||||
glider.conf
|
||||
```bash
|
||||
verbose=True
|
||||
listen=redir://:1081
|
||||
forward=http://forwarder1:8080,socks5://forwarder2:1080
|
||||
forward=http://1.1.1.1:8080
|
||||
dns=:5353
|
||||
dnsserver=8.8.8.8:53
|
||||
strategy=rr
|
||||
checkwebsite=www.apple.com
|
||||
checkinterval=30
|
||||
```
|
||||
|
||||
#### Create a ipset manually
|
||||
```bash
|
||||
ipset create myset hash:ip
|
||||
```
|
||||
|
||||
#### Config dnsmasq
|
||||
```bash
|
||||
server=/example1.com/127.0.0.1#5353
|
||||
ipset=/example1.com/myset
|
||||
server=/example2.com/127.0.0.1#5353
|
||||
ipset=/example2.com/myset
|
||||
server=/example3.com/127.0.0.1#5353
|
||||
ipset=/example4.com/myset
|
||||
```
|
||||
|
||||
#### Config iptables on your linux gateway
|
||||
```bash
|
||||
iptables -t nat -I PREROUTING -p tcp -m set --match-set myset dst -j REDIRECT --to-ports 1081
|
||||
#iptables -t nat -I OUTPUT -p tcp -m set --match-set myset dst -j REDIRECT --to-ports 1081
|
||||
```
|
||||
|
||||
#### When client requests network, the whole process:
|
||||
1. all dns requests for domain example1.com will be forward to glider(:5353) by dnsmasq
|
||||
2. glider will forward dns requests to 8.8.8.8:53 in tcp via forwarders
|
||||
3. the resolved ip address will be added to ipset "myset" by dnsmasq
|
||||
4. all tcp requests to example1.com will be redirect to glider(:1081) by iptables
|
||||
5. glider then forward requests to example1.com via forwarders
|
16
config/examples/8.transparent_proxy_with_dnsmasq/glider.conf
Normal file
16
config/examples/8.transparent_proxy_with_dnsmasq/glider.conf
Normal file
@ -0,0 +1,16 @@
|
||||
|
||||
# Verbose mode, print logs
|
||||
verbose=True
|
||||
|
||||
listen=redir://:1081
|
||||
|
||||
forward=http://forwarder1:8080,socks5://forwarder2:1080
|
||||
forward=http://1.1.1.1:8080
|
||||
|
||||
dns=:5353
|
||||
dnsserver=8.8.8.8:53
|
||||
|
||||
|
||||
strategy=rr
|
||||
checkwebsite=www.apple.com
|
||||
checkinterval=30
|
101
config/examples/9.transparent_proxy_without_dnsmasq/README.md
Normal file
101
config/examples/9.transparent_proxy_without_dnsmasq/README.md
Normal file
@ -0,0 +1,101 @@
|
||||
|
||||
## 9. Transparent Proxy without dnsmasq
|
||||
|
||||
PC Client -> Gateway with glider running(linux box) -> Upstream Forwarders -> Internet
|
||||
|
||||
#### In this mode, glider will act as the following roles:
|
||||
1. A transparent proxy server
|
||||
2. A dns forwarding server
|
||||
3. A ipset manager
|
||||
|
||||
so you don't need any dns server in your network.
|
||||
|
||||
#### Create a ipset manually
|
||||
```bash
|
||||
ipset create glider hash:net
|
||||
```
|
||||
|
||||
#### Glider Configuration
|
||||
##### glider.conf
|
||||
```bash
|
||||
verbose=True
|
||||
|
||||
# as a redir proxy
|
||||
listen=redir://:1081
|
||||
|
||||
# as a dns forwarding server
|
||||
dns=:53
|
||||
dnsserver=8.8.8.8:53
|
||||
dnsserver=8.8.4.4:53
|
||||
|
||||
# specify rule files
|
||||
rules-dir=rules.d
|
||||
```
|
||||
|
||||
##### office.rule
|
||||
```bash
|
||||
# add your forwarders
|
||||
forward=http://forwarder1:8080,socks5://forwarder2:1080
|
||||
forward=http://1.1.1.1:8080
|
||||
strategy=rr
|
||||
checkwebsite=www.apple.com
|
||||
checkinterval=30
|
||||
|
||||
# specify a different dns server(if need)
|
||||
dnsserver=208.67.222.222:53
|
||||
|
||||
# as a ipset manager
|
||||
ipset=glider
|
||||
|
||||
# specify destinations
|
||||
include=office.list
|
||||
|
||||
domain=example1.com
|
||||
domain=example2.com
|
||||
# matches ip
|
||||
ip=1.1.1.1
|
||||
ip=2.2.2.2
|
||||
# matches a ip net
|
||||
cidr=192.168.100.0/24
|
||||
cidr=172.16.100.0/24
|
||||
```
|
||||
|
||||
##### office.list
|
||||
```bash
|
||||
# destinations list
|
||||
domain=mycompany.com
|
||||
domain=mycompany1.com
|
||||
ip=4.4.4.4
|
||||
ip=5.5.5.5
|
||||
cidr=172.16.101.0/24
|
||||
cidr=172.16.102.0/24
|
||||
```
|
||||
|
||||
#### Configure iptables on your linux gateway
|
||||
```bash
|
||||
iptables -t nat -I PREROUTING -p tcp -m set --match-set glider dst -j REDIRECT --to-ports 1081
|
||||
iptables -t nat -I OUTPUT -p tcp -m set --match-set glider dst -j REDIRECT --to-ports 1081
|
||||
```
|
||||
|
||||
#### Server DNS settings
|
||||
Set server's nameserver to glider:
|
||||
```bash
|
||||
echo nameserver 127.0.0.1 > /etc/resolv.conf
|
||||
```
|
||||
|
||||
#### Client DNS settings
|
||||
Use the linux server's ip as your dns server.
|
||||
|
||||
#### When client requesting to access http://example1.com (in office.rule), the whole process:
|
||||
DNS Resolving:
|
||||
1. client sends a udp dns request to linux server, and glider will receive the request(as it listen on default dns port :53)
|
||||
2. upstream dns server choice: glider will lookup it's rule config and find out the dns server to use for this domain(matched "example1.com" in office.rule, so 208.67.222.222:53 will be chosen)
|
||||
3. glider uses the forwarder in office.rule to ask 208.67.222.222:53 for the resolve answers.
|
||||
4. glider updates it's office rule config, add the resolved ip address to it.
|
||||
5. glider adds the resolved ip into ipset "glider", and return the dns answer to client.
|
||||
|
||||
Destination Accessing:
|
||||
1. client sends http request to the resolved ip of example1.com.
|
||||
2. linux gateway server will get the request.
|
||||
3. iptabes matches the ip in ipset "glider" and redirect this request to :1081(glider)
|
||||
4. glider finds the ip in office rule, and then choose a forwarder in office.rule to complete the request.
|
@ -0,0 +1,13 @@
|
||||
|
||||
# Verbose mode, print logs
|
||||
verbose=True
|
||||
|
||||
# as a redir proxy
|
||||
listen=redir://:1081
|
||||
|
||||
# as a dns forwarding server
|
||||
dns=:53
|
||||
dnsserver=8.8.8.8:53
|
||||
|
||||
# parse all *.rule files in rules.d folder
|
||||
rules-dir=rules.d
|
@ -0,0 +1,20 @@
|
||||
|
||||
|
||||
forward=http://forwarder4:8080
|
||||
|
||||
# first connect forwarder1 then forwarder2 then internet
|
||||
forward=http://forwarder5:8080,socks5://forwarder3:1080
|
||||
|
||||
|
||||
# Round Robin mode: rr
|
||||
# High Availability mode: ha
|
||||
strategy=rr
|
||||
|
||||
checkwebsite=www.apple.com
|
||||
checkinterval=30
|
||||
|
||||
# as a ipset manager
|
||||
ipset=glider
|
||||
|
||||
# matches 192.168.0.0/16
|
||||
cidr=192.168.0.0/16
|
@ -0,0 +1,7 @@
|
||||
|
||||
domain=mycompany.com
|
||||
domain=mycompany1.com
|
||||
ip=4.4.4.4
|
||||
ip=5.5.5.5
|
||||
cidr=172.16.101.0/24
|
||||
cidr=172.16.102.0/24
|
@ -0,0 +1,31 @@
|
||||
|
||||
|
||||
forward=http://forwarder1:8080
|
||||
|
||||
# first connect forwarder2 then forwarder3 then internet
|
||||
forward=http://forwarder2:8080,socks5://forwarder3:1080
|
||||
|
||||
|
||||
# Round Robin mode: rr
|
||||
# High Availability mode: ha
|
||||
strategy=rr
|
||||
checkwebsite=www.apple.com
|
||||
checkinterval=30
|
||||
|
||||
# specify a different dns server(if need)
|
||||
dnsserver=208.67.222.222:53
|
||||
|
||||
# as a ipset manager
|
||||
ipset=glider
|
||||
|
||||
# specify destinations
|
||||
include=office.list
|
||||
|
||||
domain=example1.com
|
||||
domain=example2.com
|
||||
# matches ip
|
||||
ip=1.1.1.1
|
||||
ip=2.2.2.2
|
||||
# matches a ip net
|
||||
cidr=192.168.100.0/24
|
||||
cidr=172.16.100.0/24
|
98
config/examples/README.md
Normal file
98
config/examples/README.md
Normal file
@ -0,0 +1,98 @@
|
||||
|
||||
# Glider Configuration Examples
|
||||
|
||||
## 1. Simple Proxy Service
|
||||
Just listen on 8443 as HTTP/SOCKS5 proxy on the same port, forward all requests directly.
|
||||
|
||||
```
|
||||
Clients --> Listener --> Internet
|
||||
```
|
||||
|
||||
- [simple_proxy_service](1.simple_proxy_service)
|
||||
|
||||
## 2. One remote upstream proxy
|
||||
|
||||
```
|
||||
Clients --> Listener --> Forwarder --> Internet
|
||||
```
|
||||
|
||||
- [one_forwarder](2.one_forwarder)
|
||||
|
||||
## 3. One remote upstream PROXY CHAIN
|
||||
|
||||
```
|
||||
Clients --> Listener --> Forwarder1 --> Forwarder2 --> Internet
|
||||
```
|
||||
|
||||
- [forward_chain](3.forward_chain)
|
||||
|
||||
## 4. Multiple upstream proxies
|
||||
|
||||
```
|
||||
|Forwarder ----------------->|
|
||||
Clients --> Listener --> | | Internet
|
||||
|Forwarder --> Forwarder->...|
|
||||
```
|
||||
|
||||
- [multiple_forwarders](4.multiple_forwarders)
|
||||
|
||||
|
||||
## 5. With Rule File: Default Direct, Rule file use forwarder
|
||||
|
||||
Default:
|
||||
```
|
||||
Clients --> Listener --> Internet
|
||||
```
|
||||
Destinations specified in rule file:
|
||||
```
|
||||
|Forwarder ----------------->|
|
||||
Clients --> Listener --> | | Internet
|
||||
|Forwarder --> Forwarder->...|
|
||||
```
|
||||
|
||||
- [rule_default_direct](5.rule_default_direct)
|
||||
|
||||
|
||||
## 6. With Rule File: Default use forwarder, rule file use direct
|
||||
|
||||
Default:
|
||||
```
|
||||
|Forwarder ----------------->|
|
||||
Clients --> Listener --> | | Internet
|
||||
|Forwarder --> Forwarder->...|
|
||||
```
|
||||
|
||||
Destinations specified in rule file:
|
||||
```
|
||||
Clients --> Listener --> Internet
|
||||
```
|
||||
|
||||
- [rule_default_forwarder](6.rule_default_forwarder)
|
||||
|
||||
|
||||
## 7. With Rule File: multiple rule files
|
||||
|
||||
Default:
|
||||
```
|
||||
Clients --> Listener --> Internet
|
||||
```
|
||||
Destinations specified in rule file1:
|
||||
```
|
||||
|Forwarder1 ----------------->|
|
||||
Clients --> Listener --> | | Internet
|
||||
|Forwarder2 --> Forwarder3->...|
|
||||
```
|
||||
Destinations specified in rule file2:
|
||||
```
|
||||
|Forwarder4 ----------------->|
|
||||
Clients --> Listener --> | | Internet
|
||||
|Forwarder5 --> Forwarder6->...|
|
||||
```
|
||||
|
||||
- [rule_multiple_rule_files](7.rule_multiple_rule_files)
|
||||
|
||||
## 8. Transparent Proxy with Dnsmasq
|
||||
- [transparent_proxy_with_dnsmasq](8.transparent_proxy_with_dnsmasq)
|
||||
|
||||
## 9. Transparent Proxy without Dnsmasq
|
||||
- [transparent_proxy_without_dnsmasq](9.transparent_proxy_without_dnsmasq)
|
212
config/glider.conf.example
Normal file
212
config/glider.conf.example
Normal file
@ -0,0 +1,212 @@
|
||||
##########################################
|
||||
# __ _ _ ___ ____ ___
|
||||
# / /`_ | | | | | | \ | |_ | |_)
|
||||
# \_\_/ |_|__ |_| |_|_/ |_|__ |_| \
|
||||
#
|
||||
# Glider is a forward proxy with multiple protocols support, and also a dns forwarding server with ipset management features(like dnsmasq).
|
||||
#
|
||||
# We can set up local listeners as proxy, and forward requests to internet via forwarders.
|
||||
#
|
||||
# |Forwarder ----------------->|
|
||||
# Listener --> | | Internet
|
||||
# |Forwarder --> Forwarder->...|
|
||||
#
|
||||
# -----------------------------------------------------------
|
||||
#
|
||||
# This is a sample configuration file for glider.
|
||||
#
|
||||
# Format is one option per line, legal options are the same
|
||||
# as the options legal on the command line. See "glider -help" for details.
|
||||
#
|
||||
# Comment line starts with "#", values set in the format:
|
||||
# KEY=VALUE
|
||||
#
|
||||
# -----------------------------------------------------------
|
||||
|
||||
# Verbose mode, print logs
|
||||
verbose=True
|
||||
|
||||
# LISTENERS
|
||||
# ---------
|
||||
# Local listeners, we can set up multiple listeners on different port with
|
||||
# different protocols.
|
||||
|
||||
# listen on 8443, serve as http/socks5 proxy on the same port.
|
||||
listen=:8443
|
||||
|
||||
# listen on 8448 as a ss server.
|
||||
# listen=ss://AEAD_CHACHA20_POLY1305:pass@:8448
|
||||
|
||||
# listen on 8080 as a http proxy server.
|
||||
listen=http://:8080
|
||||
|
||||
# listen on 1080 as a socks5 proxy server.
|
||||
listen=socks5://:1080
|
||||
|
||||
# listen on 1081 as a linux transparent proxy server.
|
||||
# listen=redir://:1081
|
||||
|
||||
# listen on 1082 as a tcp tunnel, all requests to :1082 will be forward to 1.1.1.1:80
|
||||
# listen=tcptun://:1082=1.1.1.1:80
|
||||
|
||||
# listen on 1083 as a udp tunnel, all requests to :1083 will be forward to 1.1.1.1:53
|
||||
# listen=udptun://:1083=1.1.1.1:53
|
||||
|
||||
# listen on 1084 as a udp over tcp tunnel, all requests to :1084 will be forward to 1.1.1.1:53
|
||||
# listen=uottun://:1084=1.1.1.1:53
|
||||
|
||||
# http over tls (HTTPS proxy)
|
||||
# listen=tls://:443?cert=crtFilePath&key=keyFilePath,http://
|
||||
|
||||
# ss over tls
|
||||
# listen=tls://:443?cert=crtFilePath&key=keyFilePath,ss://AEAD_CHACHA20_POLY1305:pass@
|
||||
|
||||
# socks5 over unix domain socket
|
||||
# listen=unix:///tmp/glider.socket,socks5://
|
||||
|
||||
# socks5 over kcp
|
||||
# listen=kcp://aes:key@127.0.0.1:8444?dataShards=10&parityShards=3,socks5://
|
||||
|
||||
# FORWARDERS
|
||||
# ----------
|
||||
# Forwarders, we can setup multiple forwarders.
|
||||
# forward=SCHEME#OPTIONS
|
||||
|
||||
# FORWARDER OPTIONS
|
||||
# priority: set the priority of that forwarder, default:0
|
||||
# interface: set local interface or ip address used to connect remote server
|
||||
|
||||
# Socks5 proxy as forwarder
|
||||
# forward=socks5://192.168.1.10:1080
|
||||
|
||||
# Socks5 proxy as forwarder with priority 100
|
||||
# forward=socks5://192.168.1.10:1080#priority=100
|
||||
|
||||
# Socks5 proxy as forwarder with priority 100 and use `eth0` as source interface
|
||||
# forward=socks5://192.168.1.10:1080#priority=100&interface=eth0
|
||||
|
||||
# Socks5 proxy as forwarder with priority 100 and use `192.168.1.100` as source ip
|
||||
# forward=socks5://192.168.1.10:1080#priority=100&interface=192.168.1.100
|
||||
|
||||
# SS proxy as forwarder
|
||||
# forward=ss://method:pass@1.1.1.1:8443
|
||||
|
||||
# SSR proxy as forwarder
|
||||
# forward=ssr://method:pass@1.1.1.1:8443?protocol=auth_aes128_md5&protocol_param=xxx&obfs=tls1.2_ticket_auth&obfs_param=yyy
|
||||
|
||||
# http proxy as forwarder
|
||||
# forward=http://1.1.1.1:8080
|
||||
|
||||
# vmess with none security
|
||||
# forward=vmess://5a146038-0b56-4e95-b1dc-5c6f5a32cd98@1.1.1.1:443?alterID=2
|
||||
|
||||
# vmess with aes-128-gcm security
|
||||
# forward=vmess://aes-128-gcm:5a146038-0b56-4e95-b1dc-5c6f5a32cd98@1.1.1.1:443?alterID=2
|
||||
|
||||
# vmess over tls
|
||||
# forward=tls://1.1.1.1:443,vmess://5a146038-0b56-4e95-b1dc-5c6f5a32cd98@?alterID=2
|
||||
|
||||
# vmess over websocket
|
||||
# forward=ws://1.1.1.1:80/path,vmess://chacha20-poly1305:5a146038-0b56-4e95-b1dc-5c6f5a32cd98@?alterID=2
|
||||
|
||||
# vmess over ws over tls
|
||||
# forward=tls://1.1.1.1:443,ws://,vmess://5a146038-0b56-4e95-b1dc-5c6f5a32cd98@?alterID=2
|
||||
# forward=tls://1.1.1.1:443,ws://@/path,vmess://5a146038-0b56-4e95-b1dc-5c6f5a32cd98@?alterID=2
|
||||
|
||||
# ss over tls
|
||||
# forward=tls://1.1.1.1:443,ss://AEAD_CHACHA20_POLY1305:pass@
|
||||
|
||||
# ss over kcp
|
||||
# forward=kcp://aes:key@127.0.0.1:8444?dataShards=10&parityShards=3,ss://AEAD_CHACHA20_POLY1305:pass@
|
||||
|
||||
# ss with simple-obfs
|
||||
# forward=simple-obfs://1.1.1.1:443?type=tls&host=apple.com,ss://AEAD_CHACHA20_POLY1305:pass@
|
||||
|
||||
# socks5 over unix domain socket
|
||||
# forward=unix:///tmp/glider.socket,socks5://
|
||||
|
||||
# FORWARDER CHAIN
|
||||
# ---------------
|
||||
# We can setup a forward chain using 1 forward option,
|
||||
# use comma to separate different upstream forward proxies.
|
||||
#forward=http://1.1.1.1:8080,socks5://2.2.2.2:1080
|
||||
|
||||
|
||||
# FORWARDE STRATEGY
|
||||
# -----------------
|
||||
# If we set up multiple forwarders, we can use them in our own strategy.
|
||||
|
||||
# Round Robin mode: rr
|
||||
# High Availability mode: ha
|
||||
# Latency based High Availability mode: lha
|
||||
# Destination Hashing mode: dh
|
||||
strategy=rr
|
||||
|
||||
|
||||
# FORWARDERS CHECK
|
||||
# ----------------
|
||||
# We can check whether a forwarder is available.
|
||||
|
||||
# Used to connect via forwarders, if the host is unreachable, the forwarder
|
||||
# will be set to disabled.
|
||||
# MUST be a HTTP website server address, format: HOST[:PORT]. HTTPS NOT SUPPORTED.
|
||||
checkwebsite=www.apple.com
|
||||
|
||||
# check interval(seconds)
|
||||
checkinterval=30
|
||||
|
||||
# check timeout(seconds)
|
||||
checktimeout=10
|
||||
|
||||
# DNS FORWARDING SERVER
|
||||
# ----------------
|
||||
# we can specify different upstream dns server in rule file for different destinations
|
||||
|
||||
# Setup a dns forwarding server
|
||||
dns=:53
|
||||
|
||||
# global remote dns server (you can specify different dns server in rule file)
|
||||
dnsserver=8.8.8.8:53
|
||||
dnsserver=1.1.1.1:53
|
||||
|
||||
# By default, when glider received udp dns request and there's no forwarder specified,
|
||||
# it will use udp to query upstream dns servers, otherwise, use tcp;
|
||||
# you can set dnsalwaystcp=true to always use tcp no matter there is a forwarder or not.
|
||||
# dnsalwaystcp=false
|
||||
|
||||
# timeout value used in multiple dnsservers switch(seconds)
|
||||
dnstimeout=3
|
||||
|
||||
# maximum TTL value for entries in the CACHE(seconds)
|
||||
dnsmaxttl=1800
|
||||
|
||||
# minimum TTL value for entries in the CACHE(seconds)
|
||||
dnsminttl=0
|
||||
|
||||
# custom records
|
||||
dnsrecord=www.example.com/1.2.3.4
|
||||
dnsrecord=www.example.com/2606:2800:220:1:248:1893:25c8:1946
|
||||
|
||||
# INTERFACE SPECIFIC
|
||||
# ------------------
|
||||
# Specify the outbound ip/interface.
|
||||
#
|
||||
# interface=""
|
||||
# interface="192.168.1.100"
|
||||
# interface="eth0"
|
||||
|
||||
# RULE FILES
|
||||
# ----------
|
||||
# Specify additional forward rules.
|
||||
|
||||
# specify rules folder, so all *.rule files under this folder will be parsed as rule file
|
||||
rules-dir=rules.d
|
||||
|
||||
# specify a rule file
|
||||
#rulefile=office.rule
|
||||
#rulefile=home.rule
|
||||
|
||||
|
||||
# INCLUDE MORE CONFIG FILES
|
||||
#include=dnsrecord.inc.conf
|
||||
#include=more.conf
|
7
config/rules.d/direct.rule.example
Normal file
7
config/rules.d/direct.rule.example
Normal file
@ -0,0 +1,7 @@
|
||||
|
||||
# Specify destinations in rule file without forwarders, so glider will bypass
|
||||
# all forwarders and direct connect them instead
|
||||
|
||||
ip=127.0.0.1
|
||||
cidr=192.168.1.0/24
|
||||
domain=bypass.com
|
7
config/rules.d/office.list.example
Normal file
7
config/rules.d/office.list.example
Normal file
@ -0,0 +1,7 @@
|
||||
|
||||
domain=mycompany.com
|
||||
domain=mycompany1.com
|
||||
ip=4.4.4.4
|
||||
ip=5.5.5.5
|
||||
cidr=172.16.101.0/24
|
||||
cidr=172.16.102.0/24
|
52
config/rules.d/office.rule.example
Normal file
52
config/rules.d/office.rule.example
Normal file
@ -0,0 +1,52 @@
|
||||
# Glider rule configuration file.
|
||||
#
|
||||
# Format is the same as glider main config file.
|
||||
# EXCEPTION: Listeners are NOT allowed to setup here.
|
||||
|
||||
# FORWARDERS
|
||||
# ----------
|
||||
# Forwarders, we can setup multiple forwarders.
|
||||
forward=socks5://192.168.1.10:1080
|
||||
forward=ss://method:pass@1.1.1.1:8443
|
||||
forward=http://192.168.2.1:8080,socks5://192.168.2.2:1080
|
||||
|
||||
# STRATEGY for multiple forwarders. rr|ha
|
||||
strategy=rr
|
||||
|
||||
# FORWARDER CHECK SETTINGS
|
||||
checkwebsite=www.apple.com
|
||||
checkinterval=30
|
||||
|
||||
# DNS SERVER for domains in this rule file
|
||||
dnsserver=208.67.222.222:53
|
||||
|
||||
# IPSET MANAGEMENT
|
||||
# ----------------
|
||||
# Create and mange ipset on linux based on destinations in rule files
|
||||
# - add ip/cidrs in rule files on startup
|
||||
# - add resolved ips for domains in rule files by dns forwarding server
|
||||
# Usually used in transparent proxy mode on linux
|
||||
ipset=glider
|
||||
|
||||
# DESTINATIONS
|
||||
# ------------
|
||||
# ALL destinations matches the following rules will be forward using forwarders specified above
|
||||
|
||||
# INCLUDE FILE
|
||||
# we can include a list file with only destinations settings
|
||||
include=office.list
|
||||
|
||||
# matches example.com and *.example.com
|
||||
domain=example.com
|
||||
domain=example1.com
|
||||
domain=example2.com
|
||||
domain=example3.com
|
||||
|
||||
# matches ip
|
||||
ip=1.1.1.1
|
||||
ip=2.2.2.2
|
||||
ip=3.3.3.3
|
||||
|
||||
# matches a ip net
|
||||
cidr=192.168.100.0/24
|
||||
cidr=172.16.100.0/24
|
7
config/rules.d/reject.rule.example
Normal file
7
config/rules.d/reject.rule.example
Normal file
@ -0,0 +1,7 @@
|
||||
|
||||
forward=reject://
|
||||
|
||||
ipset=glider
|
||||
|
||||
domain=pornhub.com
|
||||
domain=amazon.com
|
18
dev.go
Normal file
18
dev.go
Normal file
@ -0,0 +1,18 @@
|
||||
//+build dev
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
)
|
||||
|
||||
func init() {
|
||||
go func() {
|
||||
err := http.ListenAndServe(":6060", nil)
|
||||
if err != nil {
|
||||
fmt.Printf("Create pprof server error: %s\n", err)
|
||||
}
|
||||
}()
|
||||
}
|
7
dev_linux.go
Normal file
7
dev_linux.go
Normal file
@ -0,0 +1,7 @@
|
||||
//+build dev
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "github.com/nadoo/glider/proxy/tproxy"
|
||||
)
|
66
dns/cache.go
Normal file
66
dns/cache.go
Normal file
@ -0,0 +1,66 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// LongTTL is 50 years duration in seconds, used for none-expired items.
|
||||
const LongTTL = 50 * 365 * 24 * 3600
|
||||
|
||||
type item struct {
|
||||
value []byte
|
||||
expire time.Time
|
||||
}
|
||||
|
||||
// Cache is the struct of cache.
|
||||
type Cache struct {
|
||||
m map[string]*item
|
||||
l sync.RWMutex
|
||||
}
|
||||
|
||||
// NewCache returns a new cache.
|
||||
func NewCache() (c *Cache) {
|
||||
c = &Cache{m: make(map[string]*item)}
|
||||
go func() {
|
||||
for now := range time.Tick(time.Second) {
|
||||
c.l.Lock()
|
||||
for k, v := range c.m {
|
||||
if now.After(v.expire) {
|
||||
delete(c.m, k)
|
||||
}
|
||||
}
|
||||
c.l.Unlock()
|
||||
}
|
||||
}()
|
||||
return
|
||||
}
|
||||
|
||||
// Len returns the length of cache.
|
||||
func (c *Cache) Len() int {
|
||||
return len(c.m)
|
||||
}
|
||||
|
||||
// Put an item into cache, invalid after ttl seconds.
|
||||
func (c *Cache) Put(k string, v []byte, ttl int) {
|
||||
if len(v) != 0 {
|
||||
c.l.Lock()
|
||||
it, ok := c.m[k]
|
||||
if !ok {
|
||||
it = &item{value: v}
|
||||
c.m[k] = it
|
||||
}
|
||||
it.expire = time.Now().Add(time.Duration(ttl) * time.Second)
|
||||
c.l.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// Get an item from cache.
|
||||
func (c *Cache) Get(k string) (v []byte) {
|
||||
c.l.RLock()
|
||||
if it, ok := c.m[k]; ok {
|
||||
v = it.value
|
||||
}
|
||||
c.l.RUnlock()
|
||||
return
|
||||
}
|
299
dns/client.go
Normal file
299
dns/client.go
Normal file
@ -0,0 +1,299 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/nadoo/glider/common/log"
|
||||
"github.com/nadoo/glider/proxy"
|
||||
)
|
||||
|
||||
// HandleFunc function handles the dns TypeA or TypeAAAA answer.
|
||||
type HandleFunc func(Domain, ip string) error
|
||||
|
||||
// Config for dns.
|
||||
type Config struct {
|
||||
Servers []string
|
||||
Timeout int
|
||||
MaxTTL int
|
||||
MinTTL int
|
||||
Records []string
|
||||
AlwaysTCP bool
|
||||
}
|
||||
|
||||
// Client is a dns client struct.
|
||||
type Client struct {
|
||||
proxy proxy.Proxy
|
||||
cache *Cache
|
||||
config *Config
|
||||
upServers []string
|
||||
upServerMap map[string][]string
|
||||
handlers []HandleFunc
|
||||
}
|
||||
|
||||
// NewClient returns a new dns client.
|
||||
func NewClient(proxy proxy.Proxy, config *Config) (*Client, error) {
|
||||
c := &Client{
|
||||
proxy: proxy,
|
||||
cache: NewCache(),
|
||||
config: config,
|
||||
upServers: config.Servers,
|
||||
upServerMap: make(map[string][]string),
|
||||
}
|
||||
|
||||
// custom records
|
||||
for _, record := range config.Records {
|
||||
c.AddRecord(record)
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Exchange handles request message and returns response message.
|
||||
// reqBytes = reqLen + reqMsg
|
||||
func (c *Client) Exchange(reqBytes []byte, clientAddr string, preferTCP bool) ([]byte, error) {
|
||||
req, err := UnmarshalMessage(reqBytes[2:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if req.Question.QTYPE == QTypeA || req.Question.QTYPE == QTypeAAAA {
|
||||
v := c.cache.Get(getKey(req.Question))
|
||||
if v != nil {
|
||||
binary.BigEndian.PutUint16(v[2:4], req.ID)
|
||||
log.F("[dns] %s <-> cache, type: %d, %s",
|
||||
clientAddr, req.Question.QTYPE, req.Question.QNAME)
|
||||
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
|
||||
dnsServer, network, dialerAddr, respBytes, err := c.exchange(req.Question.QNAME, reqBytes, preferTCP)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if req.Question.QTYPE != QTypeA && req.Question.QTYPE != QTypeAAAA {
|
||||
log.F("[dns] %s <-> %s(%s) via %s, type: %d, %s",
|
||||
clientAddr, dnsServer, network, dialerAddr, req.Question.QTYPE, req.Question.QNAME)
|
||||
return respBytes, nil
|
||||
}
|
||||
|
||||
resp, err := UnmarshalMessage(respBytes[2:])
|
||||
if err != nil {
|
||||
return respBytes, err
|
||||
}
|
||||
|
||||
ttl := c.config.MinTTL
|
||||
ips := []string{}
|
||||
for _, answer := range resp.Answers {
|
||||
if answer.TYPE == QTypeA || answer.TYPE == QTypeAAAA {
|
||||
for _, h := range c.handlers {
|
||||
h(resp.Question.QNAME, answer.IP)
|
||||
}
|
||||
if answer.IP != "" {
|
||||
ips = append(ips, answer.IP)
|
||||
}
|
||||
if answer.TTL != 0 {
|
||||
ttl = int(answer.TTL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ttl > c.config.MaxTTL {
|
||||
ttl = c.config.MaxTTL
|
||||
} else if ttl < c.config.MinTTL {
|
||||
ttl = c.config.MinTTL
|
||||
}
|
||||
|
||||
// add to cache only when there's a valid ip address
|
||||
if len(ips) != 0 && ttl > 0 {
|
||||
c.cache.Put(getKey(resp.Question), respBytes, ttl)
|
||||
}
|
||||
|
||||
log.F("[dns] %s <-> %s(%s) via %s, type: %d, %s: %s",
|
||||
clientAddr, dnsServer, network, dialerAddr, resp.Question.QTYPE, resp.Question.QNAME, strings.Join(ips, ","))
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
|
||||
// exchange choose a upstream dns server based on qname, communicate with it on the network.
|
||||
func (c *Client) exchange(qname string, reqBytes []byte, preferTCP bool) (
|
||||
server, network, dialerAddr string, respBytes []byte, err error) {
|
||||
|
||||
// use tcp to connect upstream server default
|
||||
network = "tcp"
|
||||
dialer := c.proxy.NextDialer(qname + ":53")
|
||||
|
||||
// if we are resolving the dialer's domain, then use Direct to avoid denpency loop
|
||||
// TODO: dialer.Addr() == "REJECT", tricky
|
||||
if strings.Contains(dialer.Addr(), qname) || dialer.Addr() == "REJECT" {
|
||||
dialer = proxy.Default
|
||||
}
|
||||
|
||||
// If client uses udp and no forwarders specified, use udp
|
||||
// TODO: dialer.Addr() == "DIRECT", tricky
|
||||
if !preferTCP && !c.config.AlwaysTCP && dialer.Addr() == "DIRECT" {
|
||||
network = "udp"
|
||||
}
|
||||
|
||||
servers := c.GetServers(qname)
|
||||
for _, server = range servers {
|
||||
var rc net.Conn
|
||||
rc, err = dialer.Dial(network, server)
|
||||
if err != nil {
|
||||
log.F("[dns] failed to connect to server %v: %v", server, err)
|
||||
continue
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
// TODO: support timeout setting for different upstream server
|
||||
rc.SetDeadline(time.Now().Add(time.Duration(c.config.Timeout) * time.Second))
|
||||
|
||||
switch network {
|
||||
case "tcp":
|
||||
respBytes, err = c.exchangeTCP(rc, reqBytes)
|
||||
case "udp":
|
||||
respBytes, err = c.exchangeUDP(rc, reqBytes)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
|
||||
log.F("[dns] failed to exchange with server %v: %v", server, err)
|
||||
}
|
||||
|
||||
return server, network, dialer.Addr(), respBytes, err
|
||||
}
|
||||
|
||||
// exchangeTCP exchange with server over tcp.
|
||||
func (c *Client) exchangeTCP(rc net.Conn, reqBytes []byte) ([]byte, error) {
|
||||
if _, err := rc.Write(reqBytes); err != nil {
|
||||
log.F("[dns] failed to write req message: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var respLen uint16
|
||||
if err := binary.Read(rc, binary.BigEndian, &respLen); err != nil {
|
||||
log.F("[dns] failed to read response length: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
respBytes := make([]byte, respLen+2)
|
||||
binary.BigEndian.PutUint16(respBytes[:2], respLen)
|
||||
|
||||
_, err := io.ReadFull(rc, respBytes[2:])
|
||||
if err != nil {
|
||||
log.F("[dns] error in read respMsg %s\n", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
|
||||
// exchangeUDP exchange with server over udp.
|
||||
func (c *Client) exchangeUDP(rc net.Conn, reqBytes []byte) ([]byte, error) {
|
||||
if _, err := rc.Write(reqBytes[2:]); err != nil {
|
||||
log.F("[dns] failed to write req message: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reqBytes = make([]byte, 2+UDPMaxLen)
|
||||
n, err := rc.Read(reqBytes[2:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
binary.BigEndian.PutUint16(reqBytes[:2], uint16(n))
|
||||
|
||||
return reqBytes[:2+n], nil
|
||||
}
|
||||
|
||||
// SetServers sets upstream dns servers for the given domain.
|
||||
func (c *Client) SetServers(domain string, servers ...string) {
|
||||
c.upServerMap[domain] = append(c.upServerMap[domain], servers...)
|
||||
}
|
||||
|
||||
// GetServers gets upstream dns servers for the given domain
|
||||
func (c *Client) GetServers(domain string) []string {
|
||||
domainParts := strings.Split(domain, ".")
|
||||
length := len(domainParts)
|
||||
for i := length - 1; i >= 0; i-- {
|
||||
domain := strings.Join(domainParts[i:length], ".")
|
||||
|
||||
if servers, ok := c.upServerMap[domain]; ok {
|
||||
return servers
|
||||
}
|
||||
}
|
||||
|
||||
return c.upServers
|
||||
}
|
||||
|
||||
// AddHandler adds a custom handler to handle the resolved result (A and AAAA).
|
||||
func (c *Client) AddHandler(h HandleFunc) {
|
||||
c.handlers = append(c.handlers, h)
|
||||
}
|
||||
|
||||
// AddRecord adds custom record to dns cache, format:
|
||||
// www.example.com/1.2.3.4 or www.example.com/2606:2800:220:1:248:1893:25c8:1946
|
||||
func (c *Client) AddRecord(record string) error {
|
||||
r := strings.Split(record, "/")
|
||||
domain, ip := r[0], r[1]
|
||||
m, err := c.GenResponse(domain, ip)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b, _ := m.Marshal()
|
||||
|
||||
var buf bytes.Buffer
|
||||
binary.Write(&buf, binary.BigEndian, uint16(len(b)))
|
||||
buf.Write(b)
|
||||
|
||||
c.cache.Put(getKey(m.Question), buf.Bytes(), LongTTL)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenResponse generates a dns response message for the given domain and ip address.
|
||||
func (c *Client) GenResponse(domain string, ip string) (*Message, error) {
|
||||
ipb := net.ParseIP(ip)
|
||||
if ipb == nil {
|
||||
return nil, errors.New("GenResponse: invalid ip format")
|
||||
}
|
||||
|
||||
var rdata []byte
|
||||
var qtype, rdlen uint16
|
||||
if rdata = ipb.To4(); rdata != nil {
|
||||
qtype = QTypeA
|
||||
rdlen = net.IPv4len
|
||||
} else {
|
||||
qtype = QTypeAAAA
|
||||
rdlen = net.IPv6len
|
||||
rdata = ipb
|
||||
}
|
||||
|
||||
m := NewMessage(0, Response)
|
||||
m.SetQuestion(NewQuestion(qtype, domain))
|
||||
rr := &RR{NAME: domain, TYPE: qtype, CLASS: ClassINET,
|
||||
TTL: uint32(c.config.MinTTL), RDLENGTH: rdlen, RDATA: rdata}
|
||||
m.AddAnswer(rr)
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func getKey(q *Question) string {
|
||||
qtype := ""
|
||||
switch q.QTYPE {
|
||||
case QTypeA:
|
||||
qtype = "A"
|
||||
case QTypeAAAA:
|
||||
qtype = "AAAA"
|
||||
}
|
||||
return q.QNAME + "/" + qtype
|
||||
}
|
467
dns/message.go
Normal file
467
dns/message.go
Normal file
@ -0,0 +1,467 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"math/rand"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// UDPMaxLen is the max size of udp dns request.
|
||||
// https://tools.ietf.org/html/rfc1035#section-4.2.1
|
||||
// Messages carried by UDP are restricted to 512 bytes (not counting the IP
|
||||
// or UDP headers). Longer messages are truncated and the TC bit is set in
|
||||
// the header.
|
||||
const UDPMaxLen = 512
|
||||
|
||||
// HeaderLen is the length of dns msg header.
|
||||
const HeaderLen = 12
|
||||
|
||||
// Message types
|
||||
const (
|
||||
Query = 0
|
||||
Response = 1
|
||||
)
|
||||
|
||||
// Query types
|
||||
const (
|
||||
QTypeA uint16 = 1 //ipv4
|
||||
QTypeAAAA uint16 = 28 ///ipv6
|
||||
)
|
||||
|
||||
// ClassINET .
|
||||
const ClassINET uint16 = 1
|
||||
|
||||
// Message format
|
||||
// https://tools.ietf.org/html/rfc1035#section-4.1
|
||||
// All communications inside of the domain protocol are carried in a single
|
||||
// format called a message. The top level format of message is divided
|
||||
// into 5 sections (some of which are empty in certain cases) shown below:
|
||||
//
|
||||
// +---------------------+
|
||||
// | Header |
|
||||
// +---------------------+
|
||||
// | Question | the question for the name server
|
||||
// +---------------------+
|
||||
// | Answer | RRs answering the question
|
||||
// +---------------------+
|
||||
// | Authority | RRs pointing toward an authority
|
||||
// +---------------------+
|
||||
// | Additional | RRs holding additional information
|
||||
type Message struct {
|
||||
Header
|
||||
// most dns implementation only support 1 question
|
||||
Question *Question
|
||||
Answers []*RR
|
||||
Authority []*RR
|
||||
Additional []*RR
|
||||
|
||||
// used in UnmarshalMessage
|
||||
unMarshaled []byte
|
||||
}
|
||||
|
||||
// NewMessage returns a new message.
|
||||
func NewMessage(id uint16, msgType int) *Message {
|
||||
if id == 0 {
|
||||
id = uint16(rand.Uint32())
|
||||
}
|
||||
|
||||
m := &Message{Header: Header{ID: id}}
|
||||
m.SetMsgType(msgType)
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// SetQuestion sets a question to dns message.
|
||||
func (m *Message) SetQuestion(q *Question) error {
|
||||
m.Question = q
|
||||
m.Header.SetQdcount(1)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddAnswer adds an answer to dns message.
|
||||
func (m *Message) AddAnswer(rr *RR) error {
|
||||
m.Answers = append(m.Answers, rr)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Marshal marshals message struct to []byte.
|
||||
func (m *Message) Marshal() ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
m.Header.SetQdcount(1)
|
||||
m.Header.SetAncount(len(m.Answers))
|
||||
|
||||
b, err := m.Header.Marshal()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf.Write(b)
|
||||
|
||||
b, err = m.Question.Marshal()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf.Write(b)
|
||||
|
||||
for _, answer := range m.Answers {
|
||||
b, err := answer.Marshal()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf.Write(b)
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalMessage unmarshals []bytes to Message.
|
||||
func UnmarshalMessage(b []byte) (*Message, error) {
|
||||
if len(b) < HeaderLen {
|
||||
return nil, errors.New("UnmarshalMessage: not enough data")
|
||||
}
|
||||
|
||||
m := &Message{unMarshaled: b}
|
||||
err := UnmarshalHeader(b[:HeaderLen], &m.Header)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
q := &Question{}
|
||||
qLen, err := m.UnmarshalQuestion(b[HeaderLen:], q)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.SetQuestion(q)
|
||||
|
||||
// resp answers
|
||||
rrIdx := HeaderLen + qLen
|
||||
for i := 0; i < int(m.Header.ANCOUNT); i++ {
|
||||
rr := &RR{}
|
||||
rrLen, err := m.UnmarshalRR(rrIdx, rr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.AddAnswer(rr)
|
||||
|
||||
rrIdx += rrLen
|
||||
}
|
||||
|
||||
m.Header.SetAncount(len(m.Answers))
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// Header format
|
||||
// https://tools.ietf.org/html/rfc1035#section-4.1.1
|
||||
// The header contains the following fields:
|
||||
//
|
||||
// 1 1 1 1 1 1
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | ID |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// |QR| Opcode |AA|TC|RD|RA| Z | RCODE |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | QDCOUNT |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | ANCOUNT |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | NSCOUNT |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | ARCOUNT |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
//
|
||||
type Header struct {
|
||||
ID uint16
|
||||
Bits uint16
|
||||
QDCOUNT uint16
|
||||
ANCOUNT uint16
|
||||
NSCOUNT uint16
|
||||
ARCOUNT uint16
|
||||
}
|
||||
|
||||
// SetMsgType sets the message type.
|
||||
func (h *Header) SetMsgType(qr int) {
|
||||
h.Bits |= uint16(qr) << 15
|
||||
}
|
||||
|
||||
// SetTC sets the tc flag.
|
||||
func (h *Header) SetTC(tc int) {
|
||||
h.Bits |= uint16(tc) << 9
|
||||
}
|
||||
|
||||
// SetQdcount sets query count, most dns servers only support 1 query per request.
|
||||
func (h *Header) SetQdcount(qdcount int) {
|
||||
h.QDCOUNT = uint16(qdcount)
|
||||
}
|
||||
|
||||
// SetAncount sets answers count.
|
||||
func (h *Header) SetAncount(ancount int) {
|
||||
h.ANCOUNT = uint16(ancount)
|
||||
}
|
||||
|
||||
func (h *Header) setFlag(QR uint16, Opcode uint16, AA uint16,
|
||||
TC uint16, RD uint16, RA uint16, RCODE uint16) {
|
||||
h.Bits = QR<<15 + Opcode<<11 + AA<<10 + TC<<9 + RD<<8 + RA<<7 + RCODE
|
||||
}
|
||||
|
||||
// Marshal marshals header struct to []byte.
|
||||
func (h *Header) Marshal() ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
err := binary.Write(&buf, binary.BigEndian, h)
|
||||
return buf.Bytes(), err
|
||||
}
|
||||
|
||||
// UnmarshalHeader unmarshals []bytes to Header.
|
||||
func UnmarshalHeader(b []byte, h *Header) error {
|
||||
if h == nil {
|
||||
return errors.New("unmarshal header must not be nil")
|
||||
}
|
||||
|
||||
if len(b) != HeaderLen {
|
||||
return errors.New("unmarshal header bytes has an unexpected size")
|
||||
}
|
||||
|
||||
h.ID = binary.BigEndian.Uint16(b[:2])
|
||||
h.Bits = binary.BigEndian.Uint16(b[2:4])
|
||||
h.QDCOUNT = binary.BigEndian.Uint16(b[4:6])
|
||||
h.ANCOUNT = binary.BigEndian.Uint16(b[6:8])
|
||||
h.NSCOUNT = binary.BigEndian.Uint16(b[8:10])
|
||||
h.ARCOUNT = binary.BigEndian.Uint16(b[10:])
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Question format
|
||||
// https://tools.ietf.org/html/rfc1035#section-4.1.2
|
||||
// The question section is used to carry the "question" in most queries,
|
||||
// i.e., the parameters that define what is being asked. The section
|
||||
// contains QDCOUNT (usually 1) entries, each of the following format:
|
||||
//
|
||||
// 1 1 1 1 1 1
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | |
|
||||
// / QNAME /
|
||||
// / /
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | QTYPE |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | QCLASS |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
type Question struct {
|
||||
QNAME string
|
||||
QTYPE uint16
|
||||
QCLASS uint16
|
||||
}
|
||||
|
||||
// NewQuestion returns a new dns question.
|
||||
func NewQuestion(qtype uint16, domain string) *Question {
|
||||
return &Question{
|
||||
QNAME: domain,
|
||||
QTYPE: qtype,
|
||||
QCLASS: ClassINET,
|
||||
}
|
||||
}
|
||||
|
||||
// Marshal marshals Question struct to []byte.
|
||||
func (q *Question) Marshal() ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
buf.Write(MarshalDomain(q.QNAME))
|
||||
binary.Write(&buf, binary.BigEndian, q.QTYPE)
|
||||
binary.Write(&buf, binary.BigEndian, q.QCLASS)
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalQuestion unmarshals []bytes to Question.
|
||||
func (m *Message) UnmarshalQuestion(b []byte, q *Question) (n int, err error) {
|
||||
if q == nil {
|
||||
return 0, errors.New("unmarshal question must not be nil")
|
||||
}
|
||||
|
||||
if len(b) <= 5 {
|
||||
return 0, errors.New("UnmarshalQuestion: not enough data")
|
||||
}
|
||||
|
||||
domain, idx, err := m.UnmarshalDomain(b)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
q.QNAME = domain
|
||||
q.QTYPE = binary.BigEndian.Uint16(b[idx : idx+2])
|
||||
q.QCLASS = binary.BigEndian.Uint16(b[idx+2 : idx+4])
|
||||
|
||||
return idx + 3 + 1, nil
|
||||
}
|
||||
|
||||
// RR format
|
||||
// https://tools.ietf.org/html/rfc1035#section-3.2.1
|
||||
// https://tools.ietf.org/html/rfc1035#section-4.1.3
|
||||
// The answer, authority, and additional sections all share the same
|
||||
// format: a variable number of resource records, where the number of
|
||||
// records is specified in the corresponding count field in the header.
|
||||
// Each resource record has the following format:
|
||||
//
|
||||
// 1 1 1 1 1 1
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | |
|
||||
// / /
|
||||
// / NAME /
|
||||
// | |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | TYPE |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | CLASS |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | TTL |
|
||||
// | |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | RDLENGTH |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
|
||||
// / RDATA /
|
||||
// / /
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
type RR struct {
|
||||
NAME string
|
||||
TYPE uint16
|
||||
CLASS uint16
|
||||
TTL uint32
|
||||
RDLENGTH uint16
|
||||
RDATA []byte
|
||||
|
||||
IP string
|
||||
}
|
||||
|
||||
// NewRR returns a new dns rr.
|
||||
func NewRR() *RR {
|
||||
rr := &RR{}
|
||||
return rr
|
||||
}
|
||||
|
||||
// Marshal marshals RR struct to []byte.
|
||||
func (rr *RR) Marshal() ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
buf.Write(MarshalDomain(rr.NAME))
|
||||
binary.Write(&buf, binary.BigEndian, rr.TYPE)
|
||||
binary.Write(&buf, binary.BigEndian, rr.CLASS)
|
||||
binary.Write(&buf, binary.BigEndian, rr.TTL)
|
||||
binary.Write(&buf, binary.BigEndian, rr.RDLENGTH)
|
||||
buf.Write(rr.RDATA)
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalRR unmarshals []bytes to RR.
|
||||
func (m *Message) UnmarshalRR(start int, rr *RR) (n int, err error) {
|
||||
if rr == nil {
|
||||
return 0, errors.New("unmarshal rr must not be nil")
|
||||
}
|
||||
|
||||
p := m.unMarshaled[start:]
|
||||
|
||||
domain, n, err := m.UnmarshalDomain(p)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
rr.NAME = domain
|
||||
|
||||
if len(p) <= n+10 {
|
||||
return 0, errors.New("UnmarshalRR: not enough data")
|
||||
}
|
||||
|
||||
rr.TYPE = binary.BigEndian.Uint16(p[n:])
|
||||
rr.CLASS = binary.BigEndian.Uint16(p[n+2:])
|
||||
rr.TTL = binary.BigEndian.Uint32(p[n+4:])
|
||||
rr.RDLENGTH = binary.BigEndian.Uint16(p[n+8:])
|
||||
|
||||
if len(p) < n+10+int(rr.RDLENGTH) {
|
||||
return 0, errors.New("UnmarshalRR: not enough data for RDATA")
|
||||
}
|
||||
|
||||
rr.RDATA = p[n+10 : n+10+int(rr.RDLENGTH)]
|
||||
|
||||
if rr.TYPE == QTypeA {
|
||||
rr.IP = net.IP(rr.RDATA[:net.IPv4len]).String()
|
||||
} else if rr.TYPE == QTypeAAAA {
|
||||
rr.IP = net.IP(rr.RDATA[:net.IPv6len]).String()
|
||||
}
|
||||
|
||||
n = n + 10 + int(rr.RDLENGTH)
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// MarshalDomain marshals domain string struct to []byte.
|
||||
func MarshalDomain(domain string) []byte {
|
||||
var buf bytes.Buffer
|
||||
|
||||
for _, seg := range strings.Split(domain, ".") {
|
||||
binary.Write(&buf, binary.BigEndian, byte(len(seg)))
|
||||
binary.Write(&buf, binary.BigEndian, []byte(seg))
|
||||
}
|
||||
binary.Write(&buf, binary.BigEndian, byte(0x00))
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// UnmarshalDomain gets domain from bytes.
|
||||
func (m *Message) UnmarshalDomain(b []byte) (string, int, error) {
|
||||
var idx, size int
|
||||
var labels = []string{}
|
||||
|
||||
for {
|
||||
// https://tools.ietf.org/html/rfc1035#section-4.1.4
|
||||
// "Message compression",
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | 1 1| OFFSET |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
if b[idx]&0xC0 == 0xC0 {
|
||||
offset := binary.BigEndian.Uint16(b[idx : idx+2])
|
||||
label, err := m.UnmarshalDomainPoint(int(offset & 0x3FFF))
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
|
||||
labels = append(labels, label)
|
||||
idx += 2
|
||||
break
|
||||
} else {
|
||||
size = int(b[idx])
|
||||
if size == 0 {
|
||||
idx++
|
||||
break
|
||||
}
|
||||
|
||||
if size > 63 {
|
||||
return "", 0, errors.New("UnmarshalDomain: label size larger than 63")
|
||||
}
|
||||
|
||||
if idx+size+1 > len(b) {
|
||||
return "", 0, errors.New("UnmarshalDomain: label size larger than msg length")
|
||||
}
|
||||
|
||||
labels = append(labels, string(b[idx+1:idx+size+1]))
|
||||
idx += (size + 1)
|
||||
}
|
||||
}
|
||||
|
||||
domain := strings.Join(labels, ".")
|
||||
return domain, idx, nil
|
||||
}
|
||||
|
||||
// UnmarshalDomainPoint gets domain from offset point.
|
||||
func (m *Message) UnmarshalDomainPoint(offset int) (string, error) {
|
||||
if offset > len(m.unMarshaled) {
|
||||
return "", errors.New("UnmarshalDomainPoint: offset larger than msg length")
|
||||
}
|
||||
domain, _, err := m.UnmarshalDomain(m.unMarshaled[offset:])
|
||||
return domain, err
|
||||
}
|
144
dns/server.go
Normal file
144
dns/server.go
Normal file
@ -0,0 +1,144 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/nadoo/glider/common/log"
|
||||
"github.com/nadoo/glider/proxy"
|
||||
)
|
||||
|
||||
// conn timeout, seconds
|
||||
const timeout = 30
|
||||
|
||||
// Server is a dns server struct.
|
||||
type Server struct {
|
||||
addr string
|
||||
// Client is used to communicate with upstream dns servers
|
||||
*Client
|
||||
}
|
||||
|
||||
// NewServer returns a new dns server.
|
||||
func NewServer(addr string, p proxy.Proxy, config *Config) (*Server, error) {
|
||||
c, err := NewClient(p, config)
|
||||
s := &Server{
|
||||
addr: addr,
|
||||
Client: c,
|
||||
}
|
||||
|
||||
return s, err
|
||||
}
|
||||
|
||||
// Start starts the dns forwarding server.
|
||||
// We use WaitGroup here to ensure both udp and tcp serer are completly running,
|
||||
// so we can start any other services later, since they may rely on dns service.
|
||||
func (s *Server) Start() {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
go s.ListenAndServeTCP(&wg)
|
||||
go s.ListenAndServeUDP(&wg)
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// ListenAndServeUDP listen and serves on udp port.
|
||||
func (s *Server) ListenAndServeUDP(wg *sync.WaitGroup) {
|
||||
c, err := net.ListenPacket("udp", s.addr)
|
||||
wg.Done()
|
||||
if err != nil {
|
||||
log.F("[dns] failed to listen on %s, error: %v", s.addr, err)
|
||||
return
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
log.F("[dns] listening UDP on %s", s.addr)
|
||||
|
||||
for {
|
||||
reqBytes := make([]byte, 2+UDPMaxLen)
|
||||
n, caddr, err := c.ReadFrom(reqBytes[2:])
|
||||
if err != nil {
|
||||
log.F("[dns] local read error: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
reqLen := uint16(n)
|
||||
if reqLen <= HeaderLen+2 {
|
||||
log.F("[dns] not enough message data")
|
||||
continue
|
||||
}
|
||||
binary.BigEndian.PutUint16(reqBytes[:2], reqLen)
|
||||
|
||||
go func() {
|
||||
respBytes, err := s.Client.Exchange(reqBytes[:2+n], caddr.String(), false)
|
||||
if err != nil {
|
||||
log.F("[dns] error in exchange: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = c.WriteTo(respBytes[2:], caddr)
|
||||
if err != nil {
|
||||
log.F("[dns] error in local write: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
}()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ListenAndServeTCP listen and serves on tcp port.
|
||||
func (s *Server) ListenAndServeTCP(wg *sync.WaitGroup) {
|
||||
l, err := net.Listen("tcp", s.addr)
|
||||
wg.Done()
|
||||
if err != nil {
|
||||
log.F("[dns-tcp] error: %v", err)
|
||||
return
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
log.F("[dns-tcp] listening TCP on %s", s.addr)
|
||||
|
||||
for {
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
log.F("[dns-tcp] error: failed to accept: %v", err)
|
||||
continue
|
||||
}
|
||||
go s.ServeTCP(c)
|
||||
}
|
||||
}
|
||||
|
||||
// ServeTCP serves a tcp connection.
|
||||
func (s *Server) ServeTCP(c net.Conn) {
|
||||
defer c.Close()
|
||||
|
||||
c.SetDeadline(time.Now().Add(time.Duration(timeout) * time.Second))
|
||||
|
||||
var reqLen uint16
|
||||
if err := binary.Read(c, binary.BigEndian, &reqLen); err != nil {
|
||||
log.F("[dns-tcp] failed to get request length: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
reqBytes := make([]byte, reqLen+2)
|
||||
_, err := io.ReadFull(c, reqBytes[2:])
|
||||
if err != nil {
|
||||
log.F("[dns-tcp] error in read reqBytes %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
binary.BigEndian.PutUint16(reqBytes[:2], reqLen)
|
||||
|
||||
respBytes, err := s.Exchange(reqBytes, c.RemoteAddr().String(), true)
|
||||
if err != nil {
|
||||
log.F("[dns-tcp] error in exchange: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := binary.Write(c, binary.BigEndian, respBytes); err != nil {
|
||||
log.F("[dns-tcp] error in local write respBytes: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
33
go.mod
Normal file
33
go.mod
Normal file
@ -0,0 +1,33 @@
|
||||
module github.com/nadoo/glider
|
||||
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/Yawning/chacha20 v0.0.0-20170904085104-e3b1f968fc63 // indirect
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
|
||||
github.com/dgryski/go-camellia v0.0.0-20140412174459-3be6b3054dd1 // indirect
|
||||
github.com/dgryski/go-idea v0.0.0-20170306091226-d2fb45a411fb // indirect
|
||||
github.com/dgryski/go-rc2 v0.0.0-20150621095337-8a9021637152 // indirect
|
||||
github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521 // indirect
|
||||
github.com/klauspost/cpuid v1.2.1 // indirect
|
||||
github.com/klauspost/reedsolomon v1.9.2 // indirect
|
||||
github.com/nadoo/conflag v0.2.0
|
||||
github.com/nadoo/go-shadowsocks2 v0.1.0
|
||||
github.com/pkg/errors v0.8.1 // indirect
|
||||
github.com/sun8911879/shadowsocksR v0.0.0-20180529042039-da20fda4804f
|
||||
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/xtaci/kcp-go v5.4.4+incompatible
|
||||
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae // indirect
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586
|
||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 // indirect
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a // indirect
|
||||
)
|
||||
|
||||
// Replace dependency modules with local developing copy
|
||||
// use `go list -m all` to confirm the final module used
|
||||
// replace (
|
||||
// github.com/nadoo/conflag => ../conflag
|
||||
// github.com/nadoo/go-shadowsocks2 => ../go-shadowsocks2
|
||||
// )
|
47
go.sum
Normal file
47
go.sum
Normal file
@ -0,0 +1,47 @@
|
||||
github.com/Yawning/chacha20 v0.0.0-20170904085104-e3b1f968fc63 h1:I6/SJSN9wJMJ+ZyQaCHUlzoTA4ypU5Bb44YWR1wTY/0=
|
||||
github.com/Yawning/chacha20 v0.0.0-20170904085104-e3b1f968fc63/go.mod h1:nf+Komq6fVP4SwmKEaVGxHTyQGKREVlwjQKpvOV39yE=
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
||||
github.com/dgryski/go-camellia v0.0.0-20140412174459-3be6b3054dd1 h1:/5UddQ9I3CXetvBVN2ipRc209YUB0AMR8bufErftAxI=
|
||||
github.com/dgryski/go-camellia v0.0.0-20140412174459-3be6b3054dd1/go.mod h1:QX5ZVULjAfZJux/W62Y91HvCh9hyW6enAwcrrv/sLj0=
|
||||
github.com/dgryski/go-idea v0.0.0-20170306091226-d2fb45a411fb h1:zXpN5126w/mhECTkqazBkrOJIMatbPP71aSIDR5UuW4=
|
||||
github.com/dgryski/go-idea v0.0.0-20170306091226-d2fb45a411fb/go.mod h1:F7WkpqJj9t98ePxB/WJGQTIDeOVPuSJ3qdn6JUjg170=
|
||||
github.com/dgryski/go-rc2 v0.0.0-20150621095337-8a9021637152 h1:ED31mPIxDJnrLt9W9dH5xgd/6KjzEACKHBVGQ33czc0=
|
||||
github.com/dgryski/go-rc2 v0.0.0-20150621095337-8a9021637152/go.mod h1:I9fhc/EvSg88cDxmfQ47v35Ssz9rlFunL/KY0A1JAYI=
|
||||
github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521 h1:fBHFH+Y/GPGFGo7LIrErQc3p2MeAhoIQNgaxPWYsSxk=
|
||||
github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:ucvhdsUCE3TH0LoLRb6ShHiJl8e39dGlx6A4g/ujlow=
|
||||
github.com/klauspost/cpuid v1.2.1 h1:vJi+O/nMdFt0vqm8NZBI6wzALWdA2X+egi0ogNyrC/w=
|
||||
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/reedsolomon v1.9.2 h1:E9CMS2Pqbv+C7tsrYad4YC9MfhnMVWhMRsTi7U0UB18=
|
||||
github.com/klauspost/reedsolomon v1.9.2/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4=
|
||||
github.com/nadoo/conflag v0.2.0 h1:xao13tYqfD+5bjQ1A/jT2kBL8tUcVpFhq3seuN5kpeM=
|
||||
github.com/nadoo/conflag v0.2.0/go.mod h1:Ayl83klaw7fagwYaI6luTmbOi4psAf7FqJNRRv5YMvU=
|
||||
github.com/nadoo/go-shadowsocks2 v0.1.0 h1:NkdUrZrI8uYq8R0YDmHLttLqKt0Z9i7dUKtGvBqZQl8=
|
||||
github.com/nadoo/go-shadowsocks2 v0.1.0/go.mod h1:J0B/QoRZtqUwE9BJqkP3F3M5+N8t+b5fXeNrkUarveM=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/sun8911879/shadowsocksR v0.0.0-20180529042039-da20fda4804f h1:66c28UIO0JbJi5he9n+QN9Ya0OAW0eKb8Eu02kMSXHI=
|
||||
github.com/sun8911879/shadowsocksR v0.0.0-20180529042039-da20fda4804f/go.mod h1:uEm3LP/z9l1+zfo2FTzUvWnxua7rbrUoGAMiLaHdujk=
|
||||
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/xtaci/kcp-go v5.4.4+incompatible h1:QIJ0a0Q0N1G20yLHL2+fpdzyy2v/Cb3PI+xiwx/KK9c=
|
||||
github.com/xtaci/kcp-go v5.4.4+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE=
|
||||
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM=
|
||||
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586 h1:7KByu05hhLed2MO29w7p1XfZvZ13m8mub3shuVftRs0=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA=
|
||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/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-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
475
ipset/ipset_linux.go
Normal file
475
ipset/ipset_linux.go
Normal file
@ -0,0 +1,475 @@
|
||||
// Apache License 2.0
|
||||
// @mdlayher https://github.com/mdlayher/netlink
|
||||
// Ref: https://github.com/vishvananda/netlink/blob/master/nl/nl_linux.go
|
||||
|
||||
package ipset
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/nadoo/glider/common/log"
|
||||
"github.com/nadoo/glider/rule"
|
||||
)
|
||||
|
||||
// NFNL_SUBSYS_IPSET netfilter netlink message types
|
||||
// https://github.com/torvalds/linux/blob/9e66317d3c92ddaab330c125dfe9d06eee268aff/include/uapi/linux/netfilter/nfnetlink.h#L56
|
||||
const NFNL_SUBSYS_IPSET = 6
|
||||
|
||||
// IPSET_PROTOCOL The protocol version
|
||||
// http://git.netfilter.org/ipset/tree/include/libipset/linux_ip_set.h
|
||||
const IPSET_PROTOCOL = 6
|
||||
|
||||
// IPSET_MAXNAMELEN The max length of strings including NUL: set and type identifiers
|
||||
const IPSET_MAXNAMELEN = 32
|
||||
|
||||
// Message types and commands
|
||||
const (
|
||||
IPSET_CMD_CREATE = 2
|
||||
IPSET_CMD_FLUSH = 4
|
||||
IPSET_CMD_ADD = 9
|
||||
IPSET_CMD_DEL = 10
|
||||
)
|
||||
|
||||
// Attributes at command level
|
||||
const (
|
||||
IPSET_ATTR_PROTOCOL = 1 /* 1: Protocol version */
|
||||
IPSET_ATTR_SETNAME = 2 /* 2: Name of the set */
|
||||
IPSET_ATTR_TYPENAME = 3 /* 3: Typename */
|
||||
IPSET_ATTR_REVISION = 4 /* 4: Settype revision */
|
||||
IPSET_ATTR_FAMILY = 5 /* 5: Settype family */
|
||||
IPSET_ATTR_DATA = 7 /* 7: Nested attributes */
|
||||
)
|
||||
|
||||
// CADT specific attributes
|
||||
const (
|
||||
IPSET_ATTR_IP = 1
|
||||
IPSET_ATTR_CIDR = 3
|
||||
)
|
||||
|
||||
// IP specific attributes
|
||||
const (
|
||||
IPSET_ATTR_IPADDR_IPV4 = 1
|
||||
IPSET_ATTR_IPADDR_IPV6 = 2
|
||||
)
|
||||
|
||||
// ATTR flags
|
||||
const (
|
||||
NLA_F_NESTED = (1 << 15)
|
||||
NLA_F_NET_BYTEORDER = (1 << 14)
|
||||
)
|
||||
|
||||
var nextSeqNr uint32
|
||||
var nativeEndian binary.ByteOrder
|
||||
|
||||
// Manager struct
|
||||
type Manager struct {
|
||||
fd int
|
||||
lsa syscall.SockaddrNetlink
|
||||
|
||||
domainSet sync.Map
|
||||
}
|
||||
|
||||
// NewManager returns a Manager
|
||||
func NewManager(rules []*rule.Config) (*Manager, error) {
|
||||
fd, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_RAW, syscall.NETLINK_NETFILTER)
|
||||
if err != nil {
|
||||
log.F("%s", err)
|
||||
return nil, err
|
||||
}
|
||||
// defer syscall.Close(fd)
|
||||
|
||||
lsa := syscall.SockaddrNetlink{
|
||||
Family: syscall.AF_NETLINK,
|
||||
}
|
||||
|
||||
if err = syscall.Bind(fd, &lsa); err != nil {
|
||||
log.F("%s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m := &Manager{fd: fd, lsa: lsa}
|
||||
|
||||
// create ipset
|
||||
for _, r := range rules {
|
||||
if r.IPSet != "" {
|
||||
CreateSet(fd, lsa, r.IPSet)
|
||||
}
|
||||
}
|
||||
|
||||
// init ipset
|
||||
for _, r := range rules {
|
||||
if r.IPSet != "" {
|
||||
for _, domain := range r.Domain {
|
||||
m.domainSet.Store(domain, r.IPSet)
|
||||
}
|
||||
for _, ip := range r.IP {
|
||||
AddToSet(fd, lsa, r.IPSet, ip)
|
||||
}
|
||||
for _, cidr := range r.CIDR {
|
||||
AddToSet(fd, lsa, r.IPSet, cidr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// AddDomainIP implements the DNSAnswerHandler function, used to update ipset according to domainSet rule
|
||||
func (m *Manager) AddDomainIP(domain, ip string) error {
|
||||
if ip != "" {
|
||||
domainParts := strings.Split(domain, ".")
|
||||
length := len(domainParts)
|
||||
for i := length - 1; i >= 0; i-- {
|
||||
domain := strings.Join(domainParts[i:length], ".")
|
||||
|
||||
// find in domainMap
|
||||
if ipset, ok := m.domainSet.Load(domain); ok {
|
||||
AddToSet(m.fd, m.lsa, ipset.(string), ip)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateSet create a ipset
|
||||
func CreateSet(fd int, lsa syscall.SockaddrNetlink, setName string) {
|
||||
if setName == "" {
|
||||
return
|
||||
}
|
||||
|
||||
if len(setName) > IPSET_MAXNAMELEN {
|
||||
log.Fatal("ipset: name too long")
|
||||
}
|
||||
|
||||
log.F("ipset create %s hash:net", setName)
|
||||
|
||||
req := NewNetlinkRequest(IPSET_CMD_CREATE|(NFNL_SUBSYS_IPSET<<8), syscall.NLM_F_REQUEST)
|
||||
|
||||
// TODO: support AF_INET6
|
||||
req.AddData(NewNfGenMsg(syscall.AF_INET, 0, 0))
|
||||
req.AddData(NewRtAttr(IPSET_ATTR_PROTOCOL, Uint8Attr(IPSET_PROTOCOL)))
|
||||
req.AddData(NewRtAttr(IPSET_ATTR_SETNAME, ZeroTerminated(setName)))
|
||||
req.AddData(NewRtAttr(IPSET_ATTR_TYPENAME, ZeroTerminated("hash:net")))
|
||||
req.AddData(NewRtAttr(IPSET_ATTR_REVISION, Uint8Attr(1)))
|
||||
req.AddData(NewRtAttr(IPSET_ATTR_FAMILY, Uint8Attr(2)))
|
||||
req.AddData(NewRtAttr(IPSET_ATTR_DATA|NLA_F_NESTED, nil))
|
||||
|
||||
err := syscall.Sendto(fd, req.Serialize(), 0, &lsa)
|
||||
if err != nil {
|
||||
log.F("%s", err)
|
||||
}
|
||||
|
||||
FlushSet(fd, lsa, setName)
|
||||
}
|
||||
|
||||
// FlushSet flush a ipset
|
||||
func FlushSet(fd int, lsa syscall.SockaddrNetlink, setName string) {
|
||||
log.F("ipset flush %s", setName)
|
||||
|
||||
req := NewNetlinkRequest(IPSET_CMD_FLUSH|(NFNL_SUBSYS_IPSET<<8), syscall.NLM_F_REQUEST)
|
||||
|
||||
// TODO: support AF_INET6
|
||||
req.AddData(NewNfGenMsg(syscall.AF_INET, 0, 0))
|
||||
req.AddData(NewRtAttr(IPSET_ATTR_PROTOCOL, Uint8Attr(IPSET_PROTOCOL)))
|
||||
req.AddData(NewRtAttr(IPSET_ATTR_SETNAME, ZeroTerminated(setName)))
|
||||
|
||||
err := syscall.Sendto(fd, req.Serialize(), 0, &lsa)
|
||||
if err != nil {
|
||||
log.F("%s", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// AddToSet adds an entry to ipset
|
||||
func AddToSet(fd int, lsa syscall.SockaddrNetlink, setName, entry string) {
|
||||
if setName == "" {
|
||||
return
|
||||
}
|
||||
|
||||
if len(setName) > IPSET_MAXNAMELEN {
|
||||
log.F("ipset: name too long")
|
||||
}
|
||||
|
||||
log.F("ipset add %s %s", setName, entry)
|
||||
|
||||
var ip net.IP
|
||||
var cidr *net.IPNet
|
||||
|
||||
ip, cidr, err := net.ParseCIDR(entry)
|
||||
if err != nil {
|
||||
ip = net.ParseIP(entry)
|
||||
}
|
||||
|
||||
if ip == nil {
|
||||
log.F("ipset: parse %s error", entry)
|
||||
return
|
||||
}
|
||||
|
||||
req := NewNetlinkRequest(IPSET_CMD_ADD|(NFNL_SUBSYS_IPSET<<8), syscall.NLM_F_REQUEST)
|
||||
|
||||
// TODO: support AF_INET6
|
||||
req.AddData(NewNfGenMsg(syscall.AF_INET, 0, 0))
|
||||
req.AddData(NewRtAttr(IPSET_ATTR_PROTOCOL, Uint8Attr(IPSET_PROTOCOL)))
|
||||
req.AddData(NewRtAttr(IPSET_ATTR_SETNAME, ZeroTerminated(setName)))
|
||||
|
||||
attrNested := NewRtAttr(IPSET_ATTR_DATA|NLA_F_NESTED, nil)
|
||||
attrIP := NewRtAttrChild(attrNested, IPSET_ATTR_IP|NLA_F_NESTED, nil)
|
||||
|
||||
// TODO: support ipV6
|
||||
NewRtAttrChild(attrIP, IPSET_ATTR_IPADDR_IPV4|NLA_F_NET_BYTEORDER, ip.To4())
|
||||
|
||||
// for cidr prefix
|
||||
if cidr != nil {
|
||||
cidrPrefix, _ := cidr.Mask.Size()
|
||||
NewRtAttrChild(attrNested, IPSET_ATTR_CIDR, Uint8Attr(uint8(cidrPrefix)))
|
||||
}
|
||||
|
||||
NewRtAttrChild(attrNested, 9|NLA_F_NET_BYTEORDER, Uint32Attr(0))
|
||||
req.AddData(attrNested)
|
||||
|
||||
err = syscall.Sendto(fd, req.Serialize(), 0, &lsa)
|
||||
if err != nil {
|
||||
log.F("%s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// NativeEndian get native endianness for the system
|
||||
func NativeEndian() binary.ByteOrder {
|
||||
if nativeEndian == nil {
|
||||
var x uint32 = 0x01020304
|
||||
if *(*byte)(unsafe.Pointer(&x)) == 0x01 {
|
||||
nativeEndian = binary.BigEndian
|
||||
} else {
|
||||
nativeEndian = binary.LittleEndian
|
||||
}
|
||||
}
|
||||
return nativeEndian
|
||||
}
|
||||
|
||||
func rtaAlignOf(attrlen int) int {
|
||||
return (attrlen + syscall.RTA_ALIGNTO - 1) & ^(syscall.RTA_ALIGNTO - 1)
|
||||
}
|
||||
|
||||
// NetlinkRequestData .
|
||||
type NetlinkRequestData interface {
|
||||
Len() int
|
||||
Serialize() []byte
|
||||
}
|
||||
|
||||
// NfGenMsg .
|
||||
type NfGenMsg struct {
|
||||
nfgenFamily uint8
|
||||
version uint8
|
||||
resID uint16
|
||||
}
|
||||
|
||||
// NewNfGenMsg .
|
||||
func NewNfGenMsg(nfgenFamily, version, resID int) *NfGenMsg {
|
||||
return &NfGenMsg{
|
||||
nfgenFamily: uint8(nfgenFamily),
|
||||
version: uint8(version),
|
||||
resID: uint16(resID),
|
||||
}
|
||||
}
|
||||
|
||||
// Len .
|
||||
func (m *NfGenMsg) Len() int {
|
||||
return rtaAlignOf(4)
|
||||
}
|
||||
|
||||
// Serialize .
|
||||
func (m *NfGenMsg) Serialize() []byte {
|
||||
native := NativeEndian()
|
||||
|
||||
length := m.Len()
|
||||
buf := make([]byte, rtaAlignOf(length))
|
||||
buf[0] = m.nfgenFamily
|
||||
buf[1] = m.version
|
||||
native.PutUint16(buf[2:4], m.resID)
|
||||
return buf
|
||||
}
|
||||
|
||||
// RtAttr Extend RtAttr to handle data and children
|
||||
type RtAttr struct {
|
||||
syscall.RtAttr
|
||||
Data []byte
|
||||
children []NetlinkRequestData
|
||||
}
|
||||
|
||||
// NewRtAttr Create a new Extended RtAttr object
|
||||
func NewRtAttr(attrType int, data []byte) *RtAttr {
|
||||
return &RtAttr{
|
||||
RtAttr: syscall.RtAttr{
|
||||
Type: uint16(attrType),
|
||||
},
|
||||
children: []NetlinkRequestData{},
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
// NewRtAttrChild Create a new RtAttr obj anc add it as a child of an existing object
|
||||
func NewRtAttrChild(parent *RtAttr, attrType int, data []byte) *RtAttr {
|
||||
attr := NewRtAttr(attrType, data)
|
||||
parent.children = append(parent.children, attr)
|
||||
return attr
|
||||
}
|
||||
|
||||
// Len .
|
||||
func (a *RtAttr) Len() int {
|
||||
if len(a.children) == 0 {
|
||||
return (syscall.SizeofRtAttr + len(a.Data))
|
||||
}
|
||||
|
||||
l := 0
|
||||
for _, child := range a.children {
|
||||
l += rtaAlignOf(child.Len())
|
||||
}
|
||||
l += syscall.SizeofRtAttr
|
||||
return rtaAlignOf(l + len(a.Data))
|
||||
}
|
||||
|
||||
// Serialize the RtAttr into a byte array
|
||||
// This can't just unsafe.cast because it must iterate through children.
|
||||
func (a *RtAttr) Serialize() []byte {
|
||||
native := NativeEndian()
|
||||
|
||||
length := a.Len()
|
||||
buf := make([]byte, rtaAlignOf(length))
|
||||
|
||||
next := 4
|
||||
if a.Data != nil {
|
||||
copy(buf[next:], a.Data)
|
||||
next += rtaAlignOf(len(a.Data))
|
||||
}
|
||||
if len(a.children) > 0 {
|
||||
for _, child := range a.children {
|
||||
childBuf := child.Serialize()
|
||||
copy(buf[next:], childBuf)
|
||||
next += rtaAlignOf(len(childBuf))
|
||||
}
|
||||
}
|
||||
|
||||
if l := uint16(length); l != 0 {
|
||||
native.PutUint16(buf[0:2], l)
|
||||
}
|
||||
native.PutUint16(buf[2:4], a.Type)
|
||||
return buf
|
||||
}
|
||||
|
||||
// NetlinkRequest .
|
||||
type NetlinkRequest struct {
|
||||
syscall.NlMsghdr
|
||||
Data []NetlinkRequestData
|
||||
RawData []byte
|
||||
}
|
||||
|
||||
// NewNetlinkRequest create a new netlink request from proto and flags
|
||||
// Note the Len value will be inaccurate once data is added until
|
||||
// the message is serialized
|
||||
func NewNetlinkRequest(proto, flags int) *NetlinkRequest {
|
||||
return &NetlinkRequest{
|
||||
NlMsghdr: syscall.NlMsghdr{
|
||||
Len: uint32(syscall.SizeofNlMsghdr),
|
||||
Type: uint16(proto),
|
||||
Flags: syscall.NLM_F_REQUEST | uint16(flags),
|
||||
Seq: atomic.AddUint32(&nextSeqNr, 1),
|
||||
// Pid: uint32(os.Getpid()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Serialize the Netlink Request into a byte array
|
||||
func (req *NetlinkRequest) Serialize() []byte {
|
||||
length := syscall.SizeofNlMsghdr
|
||||
dataBytes := make([][]byte, len(req.Data))
|
||||
for i, data := range req.Data {
|
||||
dataBytes[i] = data.Serialize()
|
||||
length = length + len(dataBytes[i])
|
||||
}
|
||||
length += len(req.RawData)
|
||||
|
||||
req.Len = uint32(length)
|
||||
b := make([]byte, length)
|
||||
hdr := (*(*[syscall.SizeofNlMsghdr]byte)(unsafe.Pointer(req)))[:]
|
||||
next := syscall.SizeofNlMsghdr
|
||||
copy(b[0:next], hdr)
|
||||
for _, data := range dataBytes {
|
||||
for _, dataByte := range data {
|
||||
b[next] = dataByte
|
||||
next = next + 1
|
||||
}
|
||||
}
|
||||
// Add the raw data if any
|
||||
if len(req.RawData) > 0 {
|
||||
copy(b[next:length], req.RawData)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// AddData add data to request
|
||||
func (req *NetlinkRequest) AddData(data NetlinkRequestData) {
|
||||
if data != nil {
|
||||
req.Data = append(req.Data, data)
|
||||
}
|
||||
}
|
||||
|
||||
// AddRawData adds raw bytes to the end of the NetlinkRequest object during serialization
|
||||
func (req *NetlinkRequest) AddRawData(data []byte) {
|
||||
if data != nil {
|
||||
req.RawData = append(req.RawData, data...)
|
||||
}
|
||||
}
|
||||
|
||||
// Uint8Attr .
|
||||
func Uint8Attr(v uint8) []byte {
|
||||
return []byte{byte(v)}
|
||||
}
|
||||
|
||||
// Uint16Attr .
|
||||
func Uint16Attr(v uint16) []byte {
|
||||
native := NativeEndian()
|
||||
bytes := make([]byte, 2)
|
||||
native.PutUint16(bytes, v)
|
||||
return bytes
|
||||
}
|
||||
|
||||
// Uint32Attr .
|
||||
func Uint32Attr(v uint32) []byte {
|
||||
native := NativeEndian()
|
||||
bytes := make([]byte, 4)
|
||||
native.PutUint32(bytes, v)
|
||||
return bytes
|
||||
}
|
||||
|
||||
// ZeroTerminated .
|
||||
func ZeroTerminated(s string) []byte {
|
||||
bytes := make([]byte, len(s)+1)
|
||||
for i := 0; i < len(s); i++ {
|
||||
bytes[i] = s[i]
|
||||
}
|
||||
bytes[len(s)] = 0
|
||||
return bytes
|
||||
}
|
||||
|
||||
// NonZeroTerminated .
|
||||
func NonZeroTerminated(s string) []byte {
|
||||
bytes := make([]byte, len(s))
|
||||
for i := 0; i < len(s); i++ {
|
||||
bytes[i] = s[i]
|
||||
}
|
||||
return bytes
|
||||
}
|
||||
|
||||
// BytesToString .
|
||||
func BytesToString(b []byte) string {
|
||||
n := bytes.Index(b, []byte{0})
|
||||
return string(b[:n])
|
||||
}
|
22
ipset/ipset_other.go
Normal file
22
ipset/ipset_other.go
Normal file
@ -0,0 +1,22 @@
|
||||
// +build !linux
|
||||
|
||||
package ipset
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/nadoo/glider/rule"
|
||||
)
|
||||
|
||||
// Manager struct
|
||||
type Manager struct{}
|
||||
|
||||
// NewManager returns a Manager
|
||||
func NewManager(rules []*rule.Config) (*Manager, error) {
|
||||
return nil, errors.New("ipset not supported on this os")
|
||||
}
|
||||
|
||||
// AddDomainIP implements the DNSAnswerHandler function
|
||||
func (m *Manager) AddDomainIP(domain, ip string) error {
|
||||
return errors.New("ipset not supported on this os")
|
||||
}
|
92
main.go
Normal file
92
main.go
Normal file
@ -0,0 +1,92 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
stdlog "log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/nadoo/glider/common/log"
|
||||
"github.com/nadoo/glider/dns"
|
||||
"github.com/nadoo/glider/ipset"
|
||||
"github.com/nadoo/glider/proxy"
|
||||
"github.com/nadoo/glider/rule"
|
||||
"github.com/nadoo/glider/strategy"
|
||||
|
||||
_ "github.com/nadoo/glider/proxy/http"
|
||||
_ "github.com/nadoo/glider/proxy/kcp"
|
||||
_ "github.com/nadoo/glider/proxy/mixed"
|
||||
_ "github.com/nadoo/glider/proxy/obfs"
|
||||
_ "github.com/nadoo/glider/proxy/reject"
|
||||
_ "github.com/nadoo/glider/proxy/socks5"
|
||||
_ "github.com/nadoo/glider/proxy/ss"
|
||||
_ "github.com/nadoo/glider/proxy/ssr"
|
||||
_ "github.com/nadoo/glider/proxy/tcptun"
|
||||
_ "github.com/nadoo/glider/proxy/tls"
|
||||
_ "github.com/nadoo/glider/proxy/udptun"
|
||||
_ "github.com/nadoo/glider/proxy/uottun"
|
||||
_ "github.com/nadoo/glider/proxy/vmess"
|
||||
_ "github.com/nadoo/glider/proxy/ws"
|
||||
)
|
||||
|
||||
var version = "0.8.2"
|
||||
|
||||
func main() {
|
||||
// read configs
|
||||
confInit()
|
||||
|
||||
// setup a log func
|
||||
log.F = func(f string, v ...interface{}) {
|
||||
if conf.Verbose {
|
||||
stdlog.Printf(f, v...)
|
||||
}
|
||||
}
|
||||
|
||||
// global rule proxy
|
||||
p := rule.NewProxy(conf.rules, strategy.NewProxy(conf.Forward, &conf.StrategyConfig))
|
||||
|
||||
// ipset manager
|
||||
ipsetM, _ := ipset.NewManager(conf.rules)
|
||||
|
||||
// check and setup dns server
|
||||
if conf.DNS != "" {
|
||||
d, err := dns.NewServer(conf.DNS, p, &conf.DNSConfig)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// rule
|
||||
for _, r := range conf.rules {
|
||||
for _, domain := range r.Domain {
|
||||
if len(r.DNSServers) > 0 {
|
||||
d.SetServers(domain, r.DNSServers...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add a handler to update proxy rules when a domain resolved
|
||||
d.AddHandler(p.AddDomainIP)
|
||||
if ipsetM != nil {
|
||||
d.AddHandler(ipsetM.AddDomainIP)
|
||||
}
|
||||
|
||||
d.Start()
|
||||
}
|
||||
|
||||
// enable checkers
|
||||
p.Check()
|
||||
|
||||
// Proxy Servers
|
||||
for _, listen := range conf.Listen {
|
||||
local, err := proxy.ServerFromURL(listen, p)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
go local.ListenAndServe()
|
||||
}
|
||||
|
||||
sigCh := make(chan os.Signal, 1)
|
||||
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-sigCh
|
||||
}
|
6
main_linux.go
Normal file
6
main_linux.go
Normal file
@ -0,0 +1,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "github.com/nadoo/glider/proxy/redir"
|
||||
_ "github.com/nadoo/glider/proxy/unix"
|
||||
)
|
55
proxy/dialer.go
Normal file
55
proxy/dialer.go
Normal file
@ -0,0 +1,55 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/nadoo/glider/common/log"
|
||||
)
|
||||
|
||||
// Dialer is used to create connection.
|
||||
type Dialer interface {
|
||||
// Addr is the dialer's addr
|
||||
Addr() string
|
||||
|
||||
// Dial connects to the given address
|
||||
Dial(network, addr string) (c net.Conn, err error)
|
||||
|
||||
// DialUDP connects to the given address
|
||||
DialUDP(network, addr string) (pc net.PacketConn, writeTo net.Addr, err error)
|
||||
}
|
||||
|
||||
// DialerCreator is a function to create dialers.
|
||||
type DialerCreator func(s string, dialer Dialer) (Dialer, error)
|
||||
|
||||
var (
|
||||
dialerMap = make(map[string]DialerCreator)
|
||||
)
|
||||
|
||||
// RegisterDialer is used to register a dialer.
|
||||
func RegisterDialer(name string, c DialerCreator) {
|
||||
dialerMap[name] = c
|
||||
}
|
||||
|
||||
// DialerFromURL calls the registered creator to create dialers.
|
||||
// dialer is the default upstream dialer so cannot be nil, we can use Default when calling this function.
|
||||
func DialerFromURL(s string, dialer Dialer) (Dialer, error) {
|
||||
if dialer == nil {
|
||||
return nil, errors.New("DialerFromURL: dialer cannot be nil")
|
||||
}
|
||||
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
log.F("parse err: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c, ok := dialerMap[strings.ToLower(u.Scheme)]
|
||||
if ok {
|
||||
return c(s, dialer)
|
||||
}
|
||||
|
||||
return nil, errors.New("unknown scheme '" + u.Scheme + "'")
|
||||
}
|
122
proxy/direct.go
Normal file
122
proxy/direct.go
Normal file
@ -0,0 +1,122 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
|
||||
"github.com/nadoo/glider/common/log"
|
||||
)
|
||||
|
||||
// Direct proxy
|
||||
type Direct struct {
|
||||
iface *net.Interface // interface specified by user
|
||||
ip net.IP
|
||||
}
|
||||
|
||||
// Default dialer
|
||||
var Default = &Direct{}
|
||||
|
||||
// NewDirect returns a Direct dialer
|
||||
func NewDirect(intface string) (*Direct, error) {
|
||||
if intface == "" {
|
||||
return &Direct{}, nil
|
||||
}
|
||||
|
||||
ip := net.ParseIP(intface)
|
||||
if ip != nil {
|
||||
return &Direct{ip: ip}, nil
|
||||
}
|
||||
|
||||
iface, err := net.InterfaceByName(intface)
|
||||
if err != nil {
|
||||
return nil, errors.New(err.Error() + ": " + intface)
|
||||
}
|
||||
|
||||
return &Direct{iface: iface}, nil
|
||||
}
|
||||
|
||||
// Addr returns forwarder's address
|
||||
func (d *Direct) Addr() string { return "DIRECT" }
|
||||
|
||||
// Dial connects to the address addr on the network net
|
||||
func (d *Direct) Dial(network, addr string) (c net.Conn, err error) {
|
||||
if d.iface == nil || d.ip != nil {
|
||||
c, err = dial(network, addr, d.ip)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for _, ip := range d.IFaceIPs() {
|
||||
c, err = dial(network, addr, ip)
|
||||
if err == nil {
|
||||
d.ip = ip
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// no ip available (so no dials made), maybe the interface link is down
|
||||
if c == nil && err == nil {
|
||||
err = errors.New("dial failed, maybe the interface link is down, please check it")
|
||||
}
|
||||
|
||||
return c, err
|
||||
}
|
||||
|
||||
func dial(network, addr string, localIP net.IP) (net.Conn, error) {
|
||||
if network == "uot" {
|
||||
network = "udp"
|
||||
}
|
||||
|
||||
var la net.Addr
|
||||
switch network {
|
||||
case "tcp":
|
||||
la = &net.TCPAddr{IP: localIP}
|
||||
case "udp":
|
||||
la = &net.UDPAddr{IP: localIP}
|
||||
}
|
||||
|
||||
dialer := &net.Dialer{LocalAddr: la}
|
||||
c, err := dialer.Dial(network, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if c, ok := c.(*net.TCPConn); ok {
|
||||
c.SetKeepAlive(true)
|
||||
}
|
||||
|
||||
return c, err
|
||||
}
|
||||
|
||||
// DialUDP connects to the given address
|
||||
func (d *Direct) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) {
|
||||
// TODO: support specifying local interface
|
||||
la := ""
|
||||
if d.ip != nil {
|
||||
la = d.ip.String() + ":0"
|
||||
}
|
||||
|
||||
pc, err := net.ListenPacket(network, la)
|
||||
if err != nil {
|
||||
log.F("ListenPacket error: %s", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
uAddr, err := net.ResolveUDPAddr("udp", addr)
|
||||
return pc, uAddr, err
|
||||
}
|
||||
|
||||
// IFaceIPs returns ip addresses according to the specified interface
|
||||
func (d *Direct) IFaceIPs() (ips []net.IP) {
|
||||
ipnets, err := d.iface.Addrs()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, ipnet := range ipnets {
|
||||
ips = append(ips, ipnet.(*net.IPNet).IP) //!ip.IsLinkLocalUnicast()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
320
proxy/http/http.go
Normal file
320
proxy/http/http.go
Normal file
@ -0,0 +1,320 @@
|
||||
// http proxy
|
||||
// NOTE: never keep-alive so the implementation can be much easier.
|
||||
|
||||
package http
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/textproto"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/nadoo/glider/common/conn"
|
||||
"github.com/nadoo/glider/common/log"
|
||||
"github.com/nadoo/glider/proxy"
|
||||
)
|
||||
|
||||
// HTTP struct.
|
||||
type HTTP struct {
|
||||
dialer proxy.Dialer
|
||||
proxy proxy.Proxy
|
||||
addr string
|
||||
user string
|
||||
password string
|
||||
pretendAsWebServer bool
|
||||
}
|
||||
|
||||
func init() {
|
||||
proxy.RegisterDialer("http", NewHTTPDialer)
|
||||
proxy.RegisterServer("http", NewHTTPServer)
|
||||
}
|
||||
|
||||
// NewHTTP returns a http proxy.
|
||||
func NewHTTP(s string, d proxy.Dialer, p proxy.Proxy) (*HTTP, error) {
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
log.F("parse err: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addr := u.Host
|
||||
user := u.User.Username()
|
||||
pass, _ := u.User.Password()
|
||||
|
||||
h := &HTTP{
|
||||
dialer: d,
|
||||
proxy: p,
|
||||
addr: addr,
|
||||
user: user,
|
||||
password: pass,
|
||||
pretendAsWebServer: false,
|
||||
}
|
||||
|
||||
pretend := u.Query().Get("pretend")
|
||||
if pretend == "true" {
|
||||
h.pretendAsWebServer = true
|
||||
}
|
||||
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// NewHTTPDialer returns a http proxy dialer.
|
||||
func NewHTTPDialer(s string, d proxy.Dialer) (proxy.Dialer, error) {
|
||||
return NewHTTP(s, d, nil)
|
||||
}
|
||||
|
||||
// NewHTTPServer returns a http proxy server.
|
||||
func NewHTTPServer(s string, p proxy.Proxy) (proxy.Server, error) {
|
||||
return NewHTTP(s, nil, p)
|
||||
}
|
||||
|
||||
// ListenAndServe listens on server's addr and serves connections.
|
||||
func (s *HTTP) ListenAndServe() {
|
||||
l, err := net.Listen("tcp", s.addr)
|
||||
if err != nil {
|
||||
log.F("[http] failed to listen on %s: %v", s.addr, err)
|
||||
return
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
log.F("[http] listening TCP on %s", s.addr)
|
||||
|
||||
for {
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
log.F("[http] failed to accept: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
go s.Serve(c)
|
||||
}
|
||||
}
|
||||
|
||||
// Serve serves a connection.
|
||||
func (s *HTTP) Serve(c net.Conn) {
|
||||
defer c.Close()
|
||||
|
||||
if c, ok := c.(*net.TCPConn); ok {
|
||||
c.SetKeepAlive(true)
|
||||
}
|
||||
|
||||
reqR := bufio.NewReader(c)
|
||||
reqTP := textproto.NewReader(reqR)
|
||||
method, requestURI, proto, ok := parseFirstLine(reqTP)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if s.pretendAsWebServer {
|
||||
fmt.Fprintf(c, "%s 404 Not Found\r\nServer: nginx\r\n\r\n404 Not Found\r\n", proto)
|
||||
log.F("[http pretender] being accessed as web server from %s", c.RemoteAddr().String())
|
||||
return
|
||||
}
|
||||
|
||||
if method == "CONNECT" {
|
||||
s.servHTTPS(method, requestURI, proto, c)
|
||||
return
|
||||
}
|
||||
|
||||
reqHeader, err := reqTP.ReadMIMEHeader()
|
||||
if err != nil {
|
||||
log.F("[http] read header error:%s", err)
|
||||
return
|
||||
}
|
||||
cleanHeaders(reqHeader)
|
||||
|
||||
// tell the remote server not to keep alive
|
||||
reqHeader.Set("Connection", "close")
|
||||
|
||||
u, err := url.ParseRequestURI(requestURI)
|
||||
if err != nil {
|
||||
log.F("[http] parse request url error: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
var tgt = u.Host
|
||||
if !strings.Contains(u.Host, ":") {
|
||||
tgt += ":80"
|
||||
}
|
||||
|
||||
rc, p, err := s.proxy.Dial("tcp", tgt)
|
||||
if err != nil {
|
||||
fmt.Fprintf(c, "%s 502 ERROR\r\n\r\n", proto)
|
||||
log.F("[http] %s <-> %s via %s, error in dial: %v", c.RemoteAddr(), tgt, p, err)
|
||||
return
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
// GET http://example.com/a/index.htm HTTP/1.1 -->
|
||||
// GET /a/index.htm HTTP/1.1
|
||||
u.Scheme = ""
|
||||
u.Host = ""
|
||||
uri := u.String()
|
||||
|
||||
var reqBuf bytes.Buffer
|
||||
writeFirstLine(&reqBuf, method, uri, proto)
|
||||
writeHeaders(&reqBuf, reqHeader)
|
||||
|
||||
// send request to remote server
|
||||
rc.Write(reqBuf.Bytes())
|
||||
|
||||
// copy the left request bytes to remote server. eg. length specificed or chunked body
|
||||
go func() {
|
||||
if _, err := reqR.Peek(1); err == nil {
|
||||
io.Copy(rc, reqR)
|
||||
rc.SetDeadline(time.Now())
|
||||
c.SetDeadline(time.Now())
|
||||
}
|
||||
}()
|
||||
|
||||
respR := bufio.NewReader(rc)
|
||||
respTP := textproto.NewReader(respR)
|
||||
proto, code, status, ok := parseFirstLine(respTP)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
respHeader, err := respTP.ReadMIMEHeader()
|
||||
if err != nil {
|
||||
log.F("[http] %s <-> %s via %s, read header error: %v", c.RemoteAddr(), tgt, p, err)
|
||||
return
|
||||
}
|
||||
|
||||
respHeader.Set("Proxy-Connection", "close")
|
||||
respHeader.Set("Connection", "close")
|
||||
|
||||
var respBuf bytes.Buffer
|
||||
writeFirstLine(&respBuf, proto, code, status)
|
||||
writeHeaders(&respBuf, respHeader)
|
||||
|
||||
log.F("[http] %s <-> %s via %s", c.RemoteAddr(), tgt, p)
|
||||
c.Write(respBuf.Bytes())
|
||||
|
||||
io.Copy(c, respR)
|
||||
|
||||
}
|
||||
|
||||
func (s *HTTP) servHTTPS(method, requestURI, proto string, c net.Conn) {
|
||||
rc, p, err := s.proxy.Dial("tcp", requestURI)
|
||||
if err != nil {
|
||||
c.Write([]byte(proto))
|
||||
c.Write([]byte(" 502 ERROR\r\n\r\n"))
|
||||
log.F("[http] %s <-> %s [c] via %s, error in dial: %v", c.RemoteAddr(), requestURI, p, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n"))
|
||||
|
||||
log.F("[http] %s <-> %s [c] via %s", c.RemoteAddr(), requestURI, p)
|
||||
|
||||
_, _, err = conn.Relay(c, rc)
|
||||
if err != nil {
|
||||
if err, ok := err.(net.Error); ok && err.Timeout() {
|
||||
return // ignore i/o timeout
|
||||
}
|
||||
log.F("[http] relay error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Addr returns forwarder's address.
|
||||
func (s *HTTP) Addr() string {
|
||||
if s.addr == "" {
|
||||
return s.dialer.Addr()
|
||||
}
|
||||
return s.addr
|
||||
}
|
||||
|
||||
// Dial connects to the address addr on the network net via the proxy.
|
||||
func (s *HTTP) Dial(network, addr string) (net.Conn, error) {
|
||||
rc, err := s.dialer.Dial(network, s.addr)
|
||||
if err != nil {
|
||||
log.F("[http] dial to %s error: %s", s.addr, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
buf.Write([]byte("CONNECT " + addr + " HTTP/1.1\r\n"))
|
||||
// TODO: add host header for compatibility?
|
||||
buf.Write([]byte("Proxy-Connection: Keep-Alive\r\n"))
|
||||
|
||||
if s.user != "" && s.password != "" {
|
||||
auth := s.user + ":" + s.password
|
||||
buf.Write([]byte("Proxy-Authorization: Basic " + base64.StdEncoding.EncodeToString([]byte(auth)) + "\r\n"))
|
||||
}
|
||||
|
||||
// header ended
|
||||
buf.Write([]byte("\r\n"))
|
||||
_, err = rc.Write(buf.Bytes())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c := conn.NewConn(rc)
|
||||
tpr := textproto.NewReader(c.Reader())
|
||||
_, code, _, ok := parseFirstLine(tpr)
|
||||
if ok && code == "200" {
|
||||
tpr.ReadMIMEHeader()
|
||||
return c, err
|
||||
}
|
||||
|
||||
if code == "407" {
|
||||
log.F("[http] authencation needed by proxy %s", s.addr)
|
||||
} else if code == "405" {
|
||||
log.F("[http] 'CONNECT' method not allowed by proxy %s", s.addr)
|
||||
}
|
||||
|
||||
return nil, errors.New("[http] can not connect remote address: " + addr + ". error code: " + code)
|
||||
}
|
||||
|
||||
// DialUDP connects to the given address via the proxy.
|
||||
func (s *HTTP) DialUDP(network, addr string) (pc net.PacketConn, writeTo net.Addr, err error) {
|
||||
return nil, nil, errors.New("http client does not support udp")
|
||||
}
|
||||
|
||||
// parseFirstLine parses "GET /foo HTTP/1.1" OR "HTTP/1.1 200 OK" into its three parts.
|
||||
func parseFirstLine(tp *textproto.Reader) (r1, r2, r3 string, ok bool) {
|
||||
line, err := tp.ReadLine()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
s1 := strings.Index(line, " ")
|
||||
s2 := strings.Index(line[s1+1:], " ")
|
||||
if s1 < 0 || s2 < 0 {
|
||||
return
|
||||
}
|
||||
s2 += s1 + 1
|
||||
return line[:s1], line[s1+1 : s2], line[s2+1:], true
|
||||
}
|
||||
|
||||
func cleanHeaders(header textproto.MIMEHeader) {
|
||||
header.Del("Proxy-Connection")
|
||||
header.Del("Connection")
|
||||
header.Del("Keep-Alive")
|
||||
header.Del("Proxy-Authenticate")
|
||||
header.Del("Proxy-Authorization")
|
||||
header.Del("TE")
|
||||
header.Del("Trailers")
|
||||
header.Del("Transfer-Encoding")
|
||||
header.Del("Upgrade")
|
||||
}
|
||||
|
||||
func writeFirstLine(buf *bytes.Buffer, s1, s2, s3 string) {
|
||||
buf.WriteString(s1 + " " + s2 + " " + s3 + "\r\n")
|
||||
}
|
||||
|
||||
func writeHeaders(buf *bytes.Buffer, header textproto.MIMEHeader) {
|
||||
for key, values := range header {
|
||||
for _, v := range values {
|
||||
buf.WriteString(key + ": " + v + "\r\n")
|
||||
}
|
||||
}
|
||||
buf.WriteString("\r\n")
|
||||
}
|
230
proxy/kcp/kcp.go
Normal file
230
proxy/kcp/kcp.go
Normal file
@ -0,0 +1,230 @@
|
||||
package kcp
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"errors"
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
kcp "github.com/xtaci/kcp-go"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
|
||||
"github.com/nadoo/glider/common/log"
|
||||
"github.com/nadoo/glider/proxy"
|
||||
)
|
||||
|
||||
// KCP struct.
|
||||
type KCP struct {
|
||||
dialer proxy.Dialer
|
||||
proxy proxy.Proxy
|
||||
addr string
|
||||
|
||||
key string
|
||||
crypt string
|
||||
block kcp.BlockCrypt
|
||||
|
||||
dataShards int
|
||||
parityShards int
|
||||
|
||||
server proxy.Server
|
||||
}
|
||||
|
||||
func init() {
|
||||
proxy.RegisterDialer("kcp", NewKCPDialer)
|
||||
proxy.RegisterServer("kcp", NewKCPServer)
|
||||
}
|
||||
|
||||
// NewKCP returns a kcp proxy struct.
|
||||
func NewKCP(s string, d proxy.Dialer, p proxy.Proxy) (*KCP, error) {
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
log.F("[kcp] parse url err: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addr := u.Host
|
||||
crypt := u.User.Username()
|
||||
key, _ := u.User.Password()
|
||||
|
||||
query := u.Query()
|
||||
|
||||
// dataShards
|
||||
dShards := query.Get("dataShards")
|
||||
if dShards == "" {
|
||||
dShards = "10"
|
||||
}
|
||||
|
||||
dataShards, err := strconv.ParseUint(dShards, 10, 32)
|
||||
if err != nil {
|
||||
log.F("[kcp] parse dataShards err: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// parityShards
|
||||
pShards := query.Get("parityShards")
|
||||
if pShards == "" {
|
||||
pShards = "3"
|
||||
}
|
||||
|
||||
parityShards, err := strconv.ParseUint(pShards, 10, 32)
|
||||
if err != nil {
|
||||
log.F("[kcp] parse parityShards err: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
k := &KCP{
|
||||
dialer: d,
|
||||
proxy: p,
|
||||
addr: addr,
|
||||
key: key,
|
||||
crypt: crypt,
|
||||
dataShards: int(dataShards),
|
||||
parityShards: int(parityShards),
|
||||
}
|
||||
|
||||
if k.crypt != "" {
|
||||
pass := pbkdf2.Key([]byte(k.key), []byte("kcp-go"), 4096, 32, sha1.New)
|
||||
var block kcp.BlockCrypt
|
||||
switch k.crypt {
|
||||
case "sm4":
|
||||
block, _ = kcp.NewSM4BlockCrypt(pass[:16])
|
||||
case "tea":
|
||||
block, _ = kcp.NewTEABlockCrypt(pass[:16])
|
||||
case "xor":
|
||||
block, _ = kcp.NewSimpleXORBlockCrypt(pass)
|
||||
case "none":
|
||||
block, _ = kcp.NewNoneBlockCrypt(pass)
|
||||
case "aes":
|
||||
block, _ = kcp.NewAESBlockCrypt(pass)
|
||||
case "aes-128":
|
||||
block, _ = kcp.NewAESBlockCrypt(pass[:16])
|
||||
case "aes-192":
|
||||
block, _ = kcp.NewAESBlockCrypt(pass[:24])
|
||||
case "blowfish":
|
||||
block, _ = kcp.NewBlowfishBlockCrypt(pass)
|
||||
case "twofish":
|
||||
block, _ = kcp.NewTwofishBlockCrypt(pass)
|
||||
case "cast5":
|
||||
block, _ = kcp.NewCast5BlockCrypt(pass[:16])
|
||||
case "3des":
|
||||
block, _ = kcp.NewTripleDESBlockCrypt(pass[:24])
|
||||
case "xtea":
|
||||
block, _ = kcp.NewXTEABlockCrypt(pass[:16])
|
||||
case "salsa20":
|
||||
block, _ = kcp.NewSalsa20BlockCrypt(pass)
|
||||
default:
|
||||
return nil, errors.New("[kcp] unknown crypt type '" + k.crypt + "'")
|
||||
}
|
||||
|
||||
k.block = block
|
||||
}
|
||||
|
||||
return k, nil
|
||||
}
|
||||
|
||||
// NewKCPDialer returns a kcp proxy dialer.
|
||||
func NewKCPDialer(s string, d proxy.Dialer) (proxy.Dialer, error) {
|
||||
return NewKCP(s, d, nil)
|
||||
}
|
||||
|
||||
// NewKCPServer returns a kcp proxy server.
|
||||
func NewKCPServer(s string, p proxy.Proxy) (proxy.Server, error) {
|
||||
transport := strings.Split(s, ",")
|
||||
|
||||
// prepare transport listener
|
||||
// TODO: check here
|
||||
if len(transport) < 2 {
|
||||
return nil, errors.New("[kcp] malformd listener:" + s)
|
||||
}
|
||||
|
||||
k, err := NewKCP(transport[0], nil, p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
k.server, err = proxy.ServerFromURL(transport[1], p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return k, nil
|
||||
}
|
||||
|
||||
// ListenAndServe listens on server's addr and serves connections.
|
||||
func (s *KCP) ListenAndServe() {
|
||||
l, err := kcp.ListenWithOptions(s.addr, s.block, s.dataShards, s.parityShards)
|
||||
if err != nil {
|
||||
log.F("[kcp] failed to listen on %s: %v", s.addr, err)
|
||||
return
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
log.F("[kcp] listening on %s", s.addr)
|
||||
|
||||
for {
|
||||
c, err := l.AcceptKCP()
|
||||
if err != nil {
|
||||
log.F("[kcp] failed to accept: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO: change them to customizable later?
|
||||
c.SetStreamMode(true)
|
||||
c.SetWriteDelay(false)
|
||||
c.SetNoDelay(0, 30, 2, 1)
|
||||
c.SetWindowSize(1024, 1024)
|
||||
c.SetMtu(1350)
|
||||
c.SetACKNoDelay(true)
|
||||
|
||||
go s.Serve(c)
|
||||
}
|
||||
}
|
||||
|
||||
// Serve serves connections.
|
||||
func (s *KCP) Serve(c net.Conn) {
|
||||
// we know the internal server will close the connection after serve
|
||||
// defer c.Close()
|
||||
|
||||
if s.server != nil {
|
||||
s.server.Serve(c)
|
||||
}
|
||||
}
|
||||
|
||||
// Addr returns forwarder's address.
|
||||
func (s *KCP) Addr() string {
|
||||
if s.addr == "" {
|
||||
return s.dialer.Addr()
|
||||
}
|
||||
return s.addr
|
||||
}
|
||||
|
||||
// Dial connects to the address addr on the network net via the proxy.
|
||||
func (s *KCP) Dial(network, addr string) (net.Conn, error) {
|
||||
// NOTE: kcp uses udp, we should dial remote server directly here
|
||||
c, err := kcp.DialWithOptions(s.addr, s.block, s.dataShards, s.parityShards)
|
||||
if err != nil {
|
||||
log.F("[kcp] dial to %s error: %s", s.addr, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: change them to customizable later?
|
||||
c.SetStreamMode(true)
|
||||
c.SetWriteDelay(false)
|
||||
c.SetNoDelay(0, 30, 2, 1)
|
||||
c.SetWindowSize(1024, 1024)
|
||||
c.SetMtu(1350)
|
||||
c.SetACKNoDelay(true)
|
||||
|
||||
c.SetDSCP(0)
|
||||
c.SetReadBuffer(4194304)
|
||||
c.SetWriteBuffer(4194304)
|
||||
|
||||
return c, err
|
||||
}
|
||||
|
||||
// DialUDP connects to the given address via the proxy.
|
||||
func (s *KCP) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) {
|
||||
return nil, nil, errors.New("kcp client does not support udp now")
|
||||
}
|
127
proxy/mixed/mixed.go
Normal file
127
proxy/mixed/mixed.go
Normal file
@ -0,0 +1,127 @@
|
||||
package mixed
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
"net/url"
|
||||
|
||||
"github.com/nadoo/glider/common/conn"
|
||||
"github.com/nadoo/glider/common/log"
|
||||
"github.com/nadoo/glider/proxy"
|
||||
"github.com/nadoo/glider/proxy/http"
|
||||
"github.com/nadoo/glider/proxy/socks5"
|
||||
)
|
||||
|
||||
// https://www.ietf.org/rfc/rfc2616.txt, http methods must be uppercase
|
||||
var httpMethods = [...][]byte{
|
||||
[]byte("GET"),
|
||||
[]byte("POST"),
|
||||
[]byte("PUT"),
|
||||
[]byte("DELETE"),
|
||||
[]byte("CONNECT"),
|
||||
[]byte("HEAD"),
|
||||
[]byte("OPTIONS"),
|
||||
[]byte("TRACE"),
|
||||
[]byte("PATCH"),
|
||||
}
|
||||
|
||||
// Mixed struct.
|
||||
type Mixed struct {
|
||||
proxy proxy.Proxy
|
||||
addr string
|
||||
|
||||
http *http.HTTP
|
||||
socks5 *socks5.Socks5
|
||||
}
|
||||
|
||||
func init() {
|
||||
proxy.RegisterServer("mixed", NewMixedServer)
|
||||
}
|
||||
|
||||
// NewMixed returns a mixed proxy.
|
||||
func NewMixed(s string, p proxy.Proxy) (*Mixed, error) {
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
log.F("parse err: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m := &Mixed{
|
||||
proxy: p,
|
||||
addr: u.Host,
|
||||
}
|
||||
|
||||
m.http, _ = http.NewHTTP(s, nil, p)
|
||||
m.socks5, _ = socks5.NewSocks5(s, nil, p)
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// NewMixedServer returns a mixed server.
|
||||
func NewMixedServer(s string, p proxy.Proxy) (proxy.Server, error) {
|
||||
return NewMixed(s, p)
|
||||
}
|
||||
|
||||
// ListenAndServe listens on server's addr and serves connections.
|
||||
func (m *Mixed) ListenAndServe() {
|
||||
go m.socks5.ListenAndServeUDP()
|
||||
|
||||
l, err := net.Listen("tcp", m.addr)
|
||||
if err != nil {
|
||||
log.F("[mixed] failed to listen on %s: %v", m.addr, err)
|
||||
return
|
||||
}
|
||||
|
||||
log.F("[mixed] listening TCP on %s", m.addr)
|
||||
|
||||
for {
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
log.F("[mixed] failed to accept: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
go m.Serve(c)
|
||||
}
|
||||
}
|
||||
|
||||
// Serve serves connections.
|
||||
func (m *Mixed) Serve(c net.Conn) {
|
||||
defer c.Close()
|
||||
|
||||
if c, ok := c.(*net.TCPConn); ok {
|
||||
c.SetKeepAlive(true)
|
||||
}
|
||||
|
||||
cc := conn.NewConn(c)
|
||||
|
||||
if m.socks5 != nil {
|
||||
head, err := cc.Peek(1)
|
||||
if err != nil {
|
||||
// log.F("[mixed] socks5 peek error: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
// check socks5, client send socksversion: 5 as the first byte
|
||||
if head[0] == socks5.Version {
|
||||
m.socks5.Serve(cc)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if m.http != nil {
|
||||
head, err := cc.Peek(8)
|
||||
if err != nil {
|
||||
log.F("[mixed] http peek error: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, method := range httpMethods {
|
||||
if bytes.HasPrefix(head, method) {
|
||||
m.http.Serve(cc)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
78
proxy/obfs/http.go
Normal file
78
proxy/obfs/http.go
Normal file
@ -0,0 +1,78 @@
|
||||
package obfs
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"net"
|
||||
)
|
||||
|
||||
// HTTPObfs struct
|
||||
type HTTPObfs struct {
|
||||
obfsHost string
|
||||
obfsURI string
|
||||
obfsUA string
|
||||
}
|
||||
|
||||
// NewHTTPObfs returns a HTTPObfs object
|
||||
func NewHTTPObfs(obfsHost, obfsURI, obfsUA string) *HTTPObfs {
|
||||
return &HTTPObfs{obfsHost, obfsURI, obfsUA}
|
||||
}
|
||||
|
||||
// HTTPObfsConn struct
|
||||
type HTTPObfsConn struct {
|
||||
*HTTPObfs
|
||||
|
||||
net.Conn
|
||||
reader io.Reader
|
||||
}
|
||||
|
||||
// NewConn returns a new obfs connection
|
||||
func (p *HTTPObfs) NewConn(c net.Conn) (net.Conn, error) {
|
||||
cc := &HTTPObfsConn{
|
||||
Conn: c,
|
||||
HTTPObfs: p,
|
||||
}
|
||||
|
||||
// send http header to remote server
|
||||
_, err := cc.writeHeader()
|
||||
return cc, err
|
||||
}
|
||||
|
||||
func (c *HTTPObfsConn) writeHeader() (int, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
buf.WriteString("GET " + c.obfsURI + " HTTP/1.1\r\n")
|
||||
buf.WriteString("Host: " + c.obfsHost + "\r\n")
|
||||
buf.WriteString("User-Agent: " + c.obfsUA + "\r\n")
|
||||
buf.WriteString("Upgrade: websocket\r\n")
|
||||
buf.WriteString("Connection: Upgrade\r\n")
|
||||
|
||||
p := make([]byte, 16)
|
||||
rand.Read(p)
|
||||
buf.WriteString("Sec-WebSocket-Key: " + base64.StdEncoding.EncodeToString(p) + "\r\n")
|
||||
|
||||
buf.WriteString("\r\n")
|
||||
|
||||
return c.Conn.Write(buf.Bytes())
|
||||
}
|
||||
|
||||
func (c *HTTPObfsConn) Read(b []byte) (n int, err error) {
|
||||
if c.reader == nil {
|
||||
r := bufio.NewReader(c.Conn)
|
||||
c.reader = r
|
||||
for {
|
||||
l, _, err := r.ReadLine()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if len(l) == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return c.reader.Read(b)
|
||||
}
|
111
proxy/obfs/obfs.go
Normal file
111
proxy/obfs/obfs.go
Normal file
@ -0,0 +1,111 @@
|
||||
// Package obfs implements simple-obfs of ss
|
||||
package obfs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/url"
|
||||
|
||||
"github.com/nadoo/glider/common/log"
|
||||
"github.com/nadoo/glider/proxy"
|
||||
)
|
||||
|
||||
// Obfs struct.
|
||||
type Obfs struct {
|
||||
dialer proxy.Dialer
|
||||
addr string
|
||||
|
||||
obfsType string
|
||||
obfsHost string
|
||||
obfsURI string
|
||||
obfsUA string
|
||||
|
||||
obfsConn func(c net.Conn) (net.Conn, error)
|
||||
}
|
||||
|
||||
func init() {
|
||||
proxy.RegisterDialer("simple-obfs", NewObfsDialer)
|
||||
}
|
||||
|
||||
// NewObfs returns a proxy struct.
|
||||
func NewObfs(s string, d proxy.Dialer) (*Obfs, error) {
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
log.F("parse url err: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addr := u.Host
|
||||
|
||||
query := u.Query()
|
||||
obfsType := query.Get("type")
|
||||
if obfsType == "" {
|
||||
obfsType = "http"
|
||||
}
|
||||
|
||||
obfsHost := query.Get("host")
|
||||
if obfsHost == "" {
|
||||
return nil, errors.New("[obfs] host cannot be null")
|
||||
}
|
||||
|
||||
obfsURI := query.Get("uri")
|
||||
if obfsURI == "" {
|
||||
obfsURI = "/"
|
||||
}
|
||||
|
||||
obfsUA := query.Get("ua")
|
||||
if obfsUA == "" {
|
||||
obfsUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36"
|
||||
}
|
||||
|
||||
p := &Obfs{
|
||||
dialer: d,
|
||||
addr: addr,
|
||||
obfsType: obfsType,
|
||||
obfsHost: obfsHost,
|
||||
obfsURI: obfsURI,
|
||||
obfsUA: obfsUA,
|
||||
}
|
||||
|
||||
switch obfsType {
|
||||
case "http":
|
||||
httpObfs := NewHTTPObfs(obfsHost, obfsURI, obfsUA)
|
||||
p.obfsConn = httpObfs.NewConn
|
||||
case "tls":
|
||||
tlsObfs := NewTLSObfs(obfsHost)
|
||||
p.obfsConn = tlsObfs.NewConn
|
||||
default:
|
||||
return nil, errors.New("[obfs] unknown obfs type: " + obfsType)
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// NewObfsDialer returns a proxy dialer.
|
||||
func NewObfsDialer(s string, dialer proxy.Dialer) (proxy.Dialer, error) {
|
||||
return NewObfs(s, dialer)
|
||||
}
|
||||
|
||||
// Addr returns forwarder's address.
|
||||
func (s *Obfs) Addr() string {
|
||||
if s.addr == "" {
|
||||
return s.dialer.Addr()
|
||||
}
|
||||
return s.addr
|
||||
}
|
||||
|
||||
// Dial connects to the address addr on the network net via the proxy.
|
||||
func (s *Obfs) Dial(network, addr string) (net.Conn, error) {
|
||||
c, err := s.dialer.Dial("tcp", s.addr)
|
||||
if err != nil {
|
||||
log.F("[obfs] dial to %s error: %s", s.addr, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.obfsConn(c)
|
||||
}
|
||||
|
||||
// DialUDP connects to the given address via the proxy.
|
||||
func (s *Obfs) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) {
|
||||
return nil, nil, errors.New("obfs client does not support udp now")
|
||||
}
|
268
proxy/obfs/tls.go
Normal file
268
proxy/obfs/tls.go
Normal file
@ -0,0 +1,268 @@
|
||||
// https://www.ietf.org/rfc/rfc5246.txt
|
||||
// https://golang.org/src/crypto/tls/handshake_messages.go
|
||||
|
||||
// NOTE:
|
||||
// https://github.com/shadowsocks/simple-obfs/blob/master/src/obfs_tls.c
|
||||
// The official obfs-server only checks 6 static bytes of client hello packet,
|
||||
// so if we send a malformed packet, e.g: set a wrong length number of extensions,
|
||||
// obfs-server will treat it as a correct packet, but in wireshak, it's malformed.
|
||||
|
||||
package obfs
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
lenSize = 2
|
||||
chunkSize = 1 << 13 // 8192
|
||||
)
|
||||
|
||||
// TLSObfs struct
|
||||
type TLSObfs struct {
|
||||
obfsHost string
|
||||
}
|
||||
|
||||
// NewTLSObfs returns a TLSObfs object
|
||||
func NewTLSObfs(obfsHost string) *TLSObfs {
|
||||
return &TLSObfs{obfsHost: obfsHost}
|
||||
}
|
||||
|
||||
// TLSObfsConn struct
|
||||
type TLSObfsConn struct {
|
||||
*TLSObfs
|
||||
|
||||
net.Conn
|
||||
reqSent bool
|
||||
reader *bufio.Reader
|
||||
buf []byte
|
||||
leftBytes int
|
||||
}
|
||||
|
||||
// NewConn returns a new obfs connection
|
||||
func (p *TLSObfs) NewConn(c net.Conn) (net.Conn, error) {
|
||||
cc := &TLSObfsConn{
|
||||
Conn: c,
|
||||
TLSObfs: p,
|
||||
buf: make([]byte, lenSize),
|
||||
}
|
||||
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
func (c *TLSObfsConn) Write(b []byte) (int, error) {
|
||||
if !c.reqSent {
|
||||
c.reqSent = true
|
||||
return c.handshake(b)
|
||||
}
|
||||
|
||||
n := len(b)
|
||||
for i := 0; i < n; i += chunkSize {
|
||||
end := i + chunkSize
|
||||
if end > n {
|
||||
end = n
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
buf.Write([]byte{0x17, 0x03, 0x03})
|
||||
binary.Write(buf, binary.BigEndian, uint16(len(b[i:end])))
|
||||
buf.Write(b[i:end])
|
||||
|
||||
_, err := c.Conn.Write(buf.Bytes())
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (c *TLSObfsConn) Read(b []byte) (int, error) {
|
||||
if c.reader == nil {
|
||||
c.reader = bufio.NewReader(c.Conn)
|
||||
// Server Hello
|
||||
// TLSv1.2 Record Layer: Handshake Protocol: Server Hello (96 bytes)
|
||||
// TLSv1.2 Record Layer: Change Cipher Spec Protocol: Change Cipher Spec (6 bytes)
|
||||
c.reader.Discard(102)
|
||||
}
|
||||
|
||||
if c.leftBytes == 0 {
|
||||
// TLSv1.2 Record Layer:
|
||||
// 1st packet: handshake encrypted message / following packets: application data
|
||||
// 1 byte: Content Type: Handshake (22) / Application Data (23)
|
||||
// 2 bytes: Version: TLS 1.2 (0x0303)
|
||||
c.reader.Discard(3)
|
||||
|
||||
// get length
|
||||
_, err := io.ReadFull(c.reader, c.buf[:lenSize])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
c.leftBytes = int(binary.BigEndian.Uint16(c.buf[:lenSize]))
|
||||
}
|
||||
|
||||
readLen := len(b)
|
||||
if readLen > c.leftBytes {
|
||||
readLen = c.leftBytes
|
||||
}
|
||||
|
||||
m, err := c.reader.Read(b[:readLen])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
c.leftBytes -= m
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (c *TLSObfsConn) handshake(b []byte) (int, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
// prepare extension & clientHello content
|
||||
bufExt, bufHello := extension(b, c.obfsHost), clientHello()
|
||||
|
||||
// prepare lengths
|
||||
extLen := bufExt.Len()
|
||||
helloLen := bufHello.Len() + 2 + extLen // 2: len(extContentLength)
|
||||
handshakeLen := 4 + helloLen // 1: len(0x01) + 3: len(clientHelloContentLength)
|
||||
|
||||
// TLS Record Layer Begin
|
||||
// Content Type: Handshake (22)
|
||||
buf.WriteByte(0x16)
|
||||
|
||||
// Version: TLS 1.0 (0x0301)
|
||||
buf.Write([]byte{0x03, 0x01})
|
||||
|
||||
// length
|
||||
binary.Write(buf, binary.BigEndian, uint16(handshakeLen))
|
||||
|
||||
// Handshake Begin
|
||||
// Handshake Type: Client Hello (1)
|
||||
buf.WriteByte(0x01)
|
||||
|
||||
// length: uint24(3 bytes), but golang doesn't have this type
|
||||
buf.Write([]byte{uint8(helloLen >> 16), uint8(helloLen >> 8), uint8(helloLen)})
|
||||
|
||||
// clientHello content
|
||||
buf.Write(bufHello.Bytes())
|
||||
|
||||
// Extension Begin
|
||||
// ext content length
|
||||
binary.Write(buf, binary.BigEndian, uint16(extLen))
|
||||
|
||||
// ext content
|
||||
buf.Write(bufExt.Bytes())
|
||||
|
||||
_, err := c.Conn.Write(buf.Bytes())
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
func clientHello() *bytes.Buffer {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
// Version: TLS 1.2 (0x0303)
|
||||
buf.Write([]byte{0x03, 0x03})
|
||||
|
||||
// Random
|
||||
// https://tools.ietf.org/id/draft-mathewson-no-gmtunixtime-00.txt
|
||||
// NOTE:
|
||||
// Most tls implementations do not deal with the first 4 bytes unix time,
|
||||
// clients do not send current time, and server do not check it,
|
||||
// golang tls client and chrome browser send random bytes instead.
|
||||
//
|
||||
binary.Write(buf, binary.BigEndian, uint32(time.Now().Unix()))
|
||||
random := make([]byte, 28)
|
||||
// The above 2 lines of codes was added to make it compatible with some server implementation,
|
||||
// if we don't need the compatibility, just use the following code instead.
|
||||
// random := make([]byte, 32)
|
||||
|
||||
rand.Read(random)
|
||||
buf.Write(random)
|
||||
|
||||
// Session ID Length: 32
|
||||
buf.WriteByte(32)
|
||||
// Session ID
|
||||
sessionID := make([]byte, 32)
|
||||
rand.Read(sessionID)
|
||||
buf.Write(sessionID)
|
||||
|
||||
// https://github.com/shadowsocks/simple-obfs/blob/7659eeccf473aa41eb294e92c32f8f60a8747325/src/obfs_tls.c#L57
|
||||
// Cipher Suites Length: 56
|
||||
binary.Write(buf, binary.BigEndian, uint16(56))
|
||||
// Cipher Suites (28 suites)
|
||||
buf.Write([]byte{
|
||||
0xc0, 0x2c, 0xc0, 0x30, 0x00, 0x9f, 0xcc, 0xa9, 0xcc, 0xa8, 0xcc, 0xaa, 0xc0, 0x2b, 0xc0, 0x2f,
|
||||
0x00, 0x9e, 0xc0, 0x24, 0xc0, 0x28, 0x00, 0x6b, 0xc0, 0x23, 0xc0, 0x27, 0x00, 0x67, 0xc0, 0x0a,
|
||||
0xc0, 0x14, 0x00, 0x39, 0xc0, 0x09, 0xc0, 0x13, 0x00, 0x33, 0x00, 0x9d, 0x00, 0x9c, 0x00, 0x3d,
|
||||
0x00, 0x3c, 0x00, 0x35, 0x00, 0x2f, 0x00, 0xff,
|
||||
})
|
||||
|
||||
// Compression Methods Length: 1
|
||||
buf.WriteByte(0x01)
|
||||
// Compression Methods (1 method)
|
||||
buf.WriteByte(0x00)
|
||||
|
||||
return buf
|
||||
}
|
||||
|
||||
func extension(b []byte, server string) *bytes.Buffer {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
// Extension: SessionTicket TLS
|
||||
buf.Write([]byte{0x00, 0x23}) // type
|
||||
// NOTE: send some data in sessionticket, the server will treat it as data too
|
||||
binary.Write(buf, binary.BigEndian, uint16(len(b))) // length
|
||||
buf.Write(b)
|
||||
|
||||
// Extension: server_name
|
||||
buf.Write([]byte{0x00, 0x00}) // type
|
||||
binary.Write(buf, binary.BigEndian, uint16(len(server)+5)) // length
|
||||
binary.Write(buf, binary.BigEndian, uint16(len(server)+3)) // Server Name list length
|
||||
buf.WriteByte(0x00) // Server Name Type: host_name (0)
|
||||
binary.Write(buf, binary.BigEndian, uint16(len(server))) // Server Name length
|
||||
buf.Write([]byte(server))
|
||||
|
||||
// https://github.com/shadowsocks/simple-obfs/blob/7659eeccf473aa41eb294e92c32f8f60a8747325/src/obfs_tls.c#L88
|
||||
// Extension: ec_point_formats (len=4)
|
||||
buf.Write([]byte{0x00, 0x0b}) // type
|
||||
binary.Write(buf, binary.BigEndian, uint16(4)) // length
|
||||
buf.WriteByte(0x03) // format length
|
||||
buf.Write([]byte{0x01, 0x00, 0x02})
|
||||
|
||||
// Extension: supported_groups (len=10)
|
||||
buf.Write([]byte{0x00, 0x0a}) // type
|
||||
binary.Write(buf, binary.BigEndian, uint16(10)) // length
|
||||
binary.Write(buf, binary.BigEndian, uint16(8)) // Supported Groups List Length: 8
|
||||
buf.Write([]byte{0x00, 0x1d, 0x00, 0x17, 0x00, 0x19, 0x00, 0x18})
|
||||
|
||||
// Extension: signature_algorithms (len=32)
|
||||
buf.Write([]byte{0x00, 0x0d}) // type
|
||||
binary.Write(buf, binary.BigEndian, uint16(32)) // length
|
||||
binary.Write(buf, binary.BigEndian, uint16(30)) // Signature Hash Algorithms Length: 30
|
||||
buf.Write([]byte{
|
||||
0x06, 0x01, 0x06, 0x02, 0x06, 0x03, 0x05, 0x01, 0x05, 0x02, 0x05, 0x03, 0x04, 0x01, 0x04, 0x02,
|
||||
0x04, 0x03, 0x03, 0x01, 0x03, 0x02, 0x03, 0x03, 0x02, 0x01, 0x02, 0x02, 0x02, 0x03,
|
||||
})
|
||||
|
||||
// Extension: encrypt_then_mac (len=0)
|
||||
buf.Write([]byte{0x00, 0x16}) // type
|
||||
binary.Write(buf, binary.BigEndian, uint16(0)) // length
|
||||
|
||||
// Extension: extended_master_secret (len=0)
|
||||
buf.Write([]byte{0x00, 0x17}) // type
|
||||
binary.Write(buf, binary.BigEndian, uint16(0)) // length
|
||||
|
||||
return buf
|
||||
}
|
15
proxy/proxy.go
Normal file
15
proxy/proxy.go
Normal file
@ -0,0 +1,15 @@
|
||||
package proxy
|
||||
|
||||
import "net"
|
||||
|
||||
// Proxy is a dialer manager
|
||||
type Proxy interface {
|
||||
// Dial connects to the given address via the proxy.
|
||||
Dial(network, addr string) (c net.Conn, proxy string, err error)
|
||||
|
||||
// DialUDP connects to the given address via the proxy.
|
||||
DialUDP(network, addr string) (pc net.PacketConn, writeTo net.Addr, err error)
|
||||
|
||||
// Get the dialer by dstAddr
|
||||
NextDialer(dstAddr string) Dialer
|
||||
}
|
183
proxy/redir/redir_linux.go
Normal file
183
proxy/redir/redir_linux.go
Normal file
@ -0,0 +1,183 @@
|
||||
// getOrigDst:
|
||||
// https://github.com/shadowsocks/go-shadowsocks2/blob/master/tcp_linux.go#L30
|
||||
|
||||
package redir
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/url"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/nadoo/glider/common/conn"
|
||||
"github.com/nadoo/glider/common/log"
|
||||
"github.com/nadoo/glider/common/socks"
|
||||
"github.com/nadoo/glider/proxy"
|
||||
)
|
||||
|
||||
const (
|
||||
// SO_ORIGINAL_DST from linux/include/uapi/linux/netfilter_ipv4.h
|
||||
SO_ORIGINAL_DST = 80
|
||||
// IP6T_SO_ORIGINAL_DST from linux/include/uapi/linux/netfilter_ipv6/ip6_tables.h
|
||||
IP6T_SO_ORIGINAL_DST = 80
|
||||
)
|
||||
|
||||
// RedirProxy struct.
|
||||
type RedirProxy struct {
|
||||
proxy proxy.Proxy
|
||||
addr string
|
||||
ipv6 bool
|
||||
}
|
||||
|
||||
func init() {
|
||||
proxy.RegisterServer("redir", NewRedirServer)
|
||||
proxy.RegisterServer("redir6", NewRedir6Server)
|
||||
}
|
||||
|
||||
// NewRedirProxy returns a redirect proxy.
|
||||
func NewRedirProxy(s string, p proxy.Proxy, ipv6 bool) (*RedirProxy, error) {
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
log.F("parse err: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addr := u.Host
|
||||
r := &RedirProxy{
|
||||
proxy: p,
|
||||
addr: addr,
|
||||
ipv6: ipv6,
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// NewRedirServer returns a redir server.
|
||||
func NewRedirServer(s string, p proxy.Proxy) (proxy.Server, error) {
|
||||
return NewRedirProxy(s, p, false)
|
||||
}
|
||||
|
||||
// NewRedir6Server returns a redir server for ipv6.
|
||||
func NewRedir6Server(s string, p proxy.Proxy) (proxy.Server, error) {
|
||||
return NewRedirProxy(s, p, true)
|
||||
}
|
||||
|
||||
// ListenAndServe listens on server's addr and serves connections.
|
||||
func (s *RedirProxy) ListenAndServe() {
|
||||
l, err := net.Listen("tcp", s.addr)
|
||||
if err != nil {
|
||||
log.F("[redir] failed to listen on %s: %v", s.addr, err)
|
||||
return
|
||||
}
|
||||
|
||||
log.F("[redir] listening TCP on %s", s.addr)
|
||||
|
||||
for {
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
log.F("[redir] failed to accept: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
go s.Serve(c)
|
||||
}
|
||||
}
|
||||
|
||||
// Serve serves connections.
|
||||
func (s *RedirProxy) Serve(c net.Conn) {
|
||||
defer c.Close()
|
||||
|
||||
if c, ok := c.(*net.TCPConn); ok {
|
||||
c.SetKeepAlive(true)
|
||||
}
|
||||
|
||||
tgt, err := getOrigDst(c, s.ipv6)
|
||||
if err != nil {
|
||||
log.F("[redir] failed to get target address: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// loop request
|
||||
if c.LocalAddr().String() == tgt.String() {
|
||||
log.F("[redir] %s <-> %s, unallowed request to redir port", c.RemoteAddr(), tgt)
|
||||
return
|
||||
}
|
||||
|
||||
rc, p, err := s.proxy.Dial("tcp", tgt.String())
|
||||
if err != nil {
|
||||
log.F("[redir] %s <-> %s via %s, error in dial: %v", c.RemoteAddr(), tgt, p, err)
|
||||
return
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
log.F("[redir] %s <-> %s via %s", c.RemoteAddr(), tgt, p)
|
||||
|
||||
_, _, err = conn.Relay(c, rc)
|
||||
if err != nil {
|
||||
if err, ok := err.(net.Error); ok && err.Timeout() {
|
||||
return // ignore i/o timeout
|
||||
}
|
||||
log.F("[redir] relay error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Get the original destination of a TCP connection.
|
||||
func getOrigDst(conn net.Conn, ipv6 bool) (socks.Addr, error) {
|
||||
c, ok := conn.(*net.TCPConn)
|
||||
if !ok {
|
||||
return nil, errors.New("only work with TCP connection")
|
||||
}
|
||||
f, err := c.File()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
fd := f.Fd()
|
||||
|
||||
// The File() call above puts both the original socket fd and the file fd in blocking mode.
|
||||
// Set the file fd back to non-blocking mode and the original socket fd will become non-blocking as well.
|
||||
// Otherwise blocking I/O will waste OS threads.
|
||||
if err := syscall.SetNonblock(int(fd), true); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ipv6 {
|
||||
return getorigdstIPv6(fd)
|
||||
}
|
||||
|
||||
return getorigdst(fd)
|
||||
}
|
||||
|
||||
// Call getorigdst() from linux/net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c
|
||||
func getorigdst(fd uintptr) (socks.Addr, error) {
|
||||
raw := syscall.RawSockaddrInet4{}
|
||||
siz := unsafe.Sizeof(raw)
|
||||
if err := socketcall(GETSOCKOPT, fd, syscall.IPPROTO_IP, SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&raw)), uintptr(unsafe.Pointer(&siz)), 0); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addr := make([]byte, 1+net.IPv4len+2)
|
||||
addr[0] = socks.ATypIP4
|
||||
copy(addr[1:1+net.IPv4len], raw.Addr[:])
|
||||
port := (*[2]byte)(unsafe.Pointer(&raw.Port)) // big-endian
|
||||
addr[1+net.IPv4len], addr[1+net.IPv4len+1] = port[0], port[1]
|
||||
return addr, nil
|
||||
}
|
||||
|
||||
// Call ipv6_getorigdst() from linux/net/ipv6/netfilter/nf_conntrack_l3proto_ipv6.c
|
||||
func getorigdstIPv6(fd uintptr) (socks.Addr, error) {
|
||||
raw := syscall.RawSockaddrInet6{}
|
||||
siz := unsafe.Sizeof(raw)
|
||||
if err := socketcall(GETSOCKOPT, fd, syscall.IPPROTO_IPV6, IP6T_SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&raw)), uintptr(unsafe.Pointer(&siz)), 0); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addr := make([]byte, 1+net.IPv6len+2)
|
||||
addr[0] = socks.ATypIP6
|
||||
copy(addr[1:1+net.IPv6len], raw.Addr[:])
|
||||
port := (*[2]byte)(unsafe.Pointer(&raw.Port)) // big-endian
|
||||
addr[1+net.IPv6len], addr[1+net.IPv6len+1] = port[0], port[1]
|
||||
return addr, nil
|
||||
}
|
18
proxy/redir/redir_linux_386.go
Normal file
18
proxy/redir/redir_linux_386.go
Normal file
@ -0,0 +1,18 @@
|
||||
package redir
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// https://github.com/golang/go/blob/9e6b79a5dfb2f6fe4301ced956419a0da83bd025/src/syscall/syscall_linux_386.go#L196
|
||||
const GETSOCKOPT = 15
|
||||
|
||||
func socketcall(call, a0, a1, a2, a3, a4, a5 uintptr) error {
|
||||
var a [6]uintptr
|
||||
a[0], a[1], a[2], a[3], a[4], a[5] = a0, a1, a2, a3, a4, a5
|
||||
if _, _, errno := syscall.Syscall6(syscall.SYS_SOCKETCALL, call, uintptr(unsafe.Pointer(&a)), 0, 0, 0, 0); errno != 0 {
|
||||
return errno
|
||||
}
|
||||
return nil
|
||||
}
|
15
proxy/redir/redir_linux_other.go
Normal file
15
proxy/redir/redir_linux_other.go
Normal file
@ -0,0 +1,15 @@
|
||||
// +build linux,!386
|
||||
|
||||
package redir
|
||||
|
||||
import "syscall"
|
||||
|
||||
// GETSOCKOPT from syscall
|
||||
const GETSOCKOPT = syscall.SYS_GETSOCKOPT
|
||||
|
||||
func socketcall(call, a0, a1, a2, a3, a4, a5 uintptr) error {
|
||||
if _, _, errno := syscall.Syscall6(call, a0, a1, a2, a3, a4, a5); errno != 0 {
|
||||
return errno
|
||||
}
|
||||
return nil
|
||||
}
|
39
proxy/reject/reject.go
Normal file
39
proxy/reject/reject.go
Normal file
@ -0,0 +1,39 @@
|
||||
// Package reject implements a virtual proxy which always reject requests.
|
||||
package reject
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
|
||||
"github.com/nadoo/glider/proxy"
|
||||
)
|
||||
|
||||
// A Reject represents the base struct of a reject proxy.
|
||||
type Reject struct{}
|
||||
|
||||
func init() {
|
||||
proxy.RegisterDialer("reject", NewRejectDialer)
|
||||
}
|
||||
|
||||
// NewReject returns a reject proxy, reject://.
|
||||
func NewReject(s string, d proxy.Dialer) (*Reject, error) {
|
||||
return &Reject{}, nil
|
||||
}
|
||||
|
||||
// NewRejectDialer returns a reject proxy dialer.
|
||||
func NewRejectDialer(s string, d proxy.Dialer) (proxy.Dialer, error) {
|
||||
return NewReject(s, d)
|
||||
}
|
||||
|
||||
// Addr returns forwarder's address.
|
||||
func (s *Reject) Addr() string { return "REJECT" }
|
||||
|
||||
// Dial connects to the address addr on the network net via the proxy.
|
||||
func (s *Reject) Dial(network, addr string) (net.Conn, error) {
|
||||
return nil, errors.New("REJECT")
|
||||
}
|
||||
|
||||
// DialUDP connects to the given address via the proxy.
|
||||
func (s *Reject) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) {
|
||||
return nil, nil, errors.New("REJECT")
|
||||
}
|
56
proxy/server.go
Normal file
56
proxy/server.go
Normal file
@ -0,0 +1,56 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/nadoo/glider/common/log"
|
||||
)
|
||||
|
||||
// Server interface
|
||||
type Server interface {
|
||||
// ListenAndServe sets up a listener and serve on it
|
||||
ListenAndServe()
|
||||
|
||||
// Serve serves a connection
|
||||
Serve(c net.Conn)
|
||||
}
|
||||
|
||||
// ServerCreator is a function to create proxy servers
|
||||
type ServerCreator func(s string, proxy Proxy) (Server, error)
|
||||
|
||||
var (
|
||||
serverMap = make(map[string]ServerCreator)
|
||||
)
|
||||
|
||||
// RegisterServer is used to register a proxy server
|
||||
func RegisterServer(name string, c ServerCreator) {
|
||||
serverMap[name] = c
|
||||
}
|
||||
|
||||
// ServerFromURL calls the registered creator to create proxy servers
|
||||
// dialer is the default upstream dialer so cannot be nil, we can use Default when calling this function
|
||||
func ServerFromURL(s string, p Proxy) (Server, error) {
|
||||
if p == nil {
|
||||
return nil, errors.New("ServerFromURL: dialer cannot be nil")
|
||||
}
|
||||
|
||||
if !strings.Contains(s, "://") {
|
||||
s = "mixed://" + s
|
||||
}
|
||||
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
log.F("parse err: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c, ok := serverMap[strings.ToLower(u.Scheme)]
|
||||
if ok {
|
||||
return c(s, p)
|
||||
}
|
||||
|
||||
return nil, errors.New("unknown scheme '" + u.Scheme + "'")
|
||||
}
|
99
proxy/socks5/packet.go
Normal file
99
proxy/socks5/packet.go
Normal file
@ -0,0 +1,99 @@
|
||||
package socks5
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/nadoo/glider/common/log"
|
||||
"github.com/nadoo/glider/common/socks"
|
||||
)
|
||||
|
||||
// PktConn .
|
||||
type PktConn struct {
|
||||
net.PacketConn
|
||||
|
||||
writeAddr net.Addr // write to and read from addr
|
||||
|
||||
tgtAddr socks.Addr
|
||||
tgtHeader bool
|
||||
|
||||
ctrlConn net.Conn // tcp control conn
|
||||
}
|
||||
|
||||
// NewPktConn returns a PktConn
|
||||
func NewPktConn(c net.PacketConn, writeAddr net.Addr, tgtAddr socks.Addr, tgtHeader bool, ctrlConn net.Conn) *PktConn {
|
||||
pc := &PktConn{
|
||||
PacketConn: c,
|
||||
writeAddr: writeAddr,
|
||||
tgtAddr: tgtAddr,
|
||||
tgtHeader: tgtHeader,
|
||||
ctrlConn: ctrlConn}
|
||||
|
||||
if ctrlConn != nil {
|
||||
go func() {
|
||||
buf := make([]byte, 1)
|
||||
for {
|
||||
_, err := ctrlConn.Read(buf)
|
||||
if err, ok := err.(net.Error); ok && err.Timeout() {
|
||||
continue
|
||||
}
|
||||
log.F("[socks5] dialudp udp associate end")
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
return pc
|
||||
}
|
||||
|
||||
// ReadFrom overrides the original function from net.PacketConn
|
||||
func (pc *PktConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||
if !pc.tgtHeader {
|
||||
return pc.PacketConn.ReadFrom(b)
|
||||
}
|
||||
|
||||
buf := make([]byte, len(b))
|
||||
n, raddr, err := pc.PacketConn.ReadFrom(buf)
|
||||
if err != nil {
|
||||
return n, raddr, err
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc1928#section-7
|
||||
// +----+------+------+----------+----------+----------+
|
||||
// |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA |
|
||||
// +----+------+------+----------+----------+----------+
|
||||
// | 2 | 1 | 1 | Variable | 2 | Variable |
|
||||
// +----+------+------+----------+----------+----------+
|
||||
tgtAddr := socks.SplitAddr(buf[3:])
|
||||
copy(b, buf[3+len(tgtAddr):])
|
||||
|
||||
//test
|
||||
if pc.writeAddr == nil {
|
||||
pc.writeAddr = raddr
|
||||
}
|
||||
|
||||
if pc.tgtAddr == nil {
|
||||
pc.tgtAddr = tgtAddr
|
||||
}
|
||||
|
||||
return n - len(tgtAddr) - 3, raddr, err
|
||||
}
|
||||
|
||||
// WriteTo overrides the original function from net.PacketConn
|
||||
func (pc *PktConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||
if !pc.tgtHeader {
|
||||
return pc.PacketConn.WriteTo(b, addr)
|
||||
}
|
||||
|
||||
buf := append([]byte{0, 0, 0}, pc.tgtAddr...)
|
||||
buf = append(buf, b[:]...)
|
||||
return pc.PacketConn.WriteTo(buf, pc.writeAddr)
|
||||
}
|
||||
|
||||
// Close .
|
||||
func (pc *PktConn) Close() error {
|
||||
if pc.ctrlConn != nil {
|
||||
pc.ctrlConn.Close()
|
||||
}
|
||||
|
||||
return pc.PacketConn.Close()
|
||||
}
|
467
proxy/socks5/socks5.go
Normal file
467
proxy/socks5/socks5.go
Normal file
@ -0,0 +1,467 @@
|
||||
// https://tools.ietf.org/html/rfc1928
|
||||
|
||||
// socks5 client:
|
||||
// https://github.com/golang/net/tree/master/proxy
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// socks5 server:
|
||||
// https://github.com/shadowsocks/go-shadowsocks2/tree/master/socks
|
||||
|
||||
// Package socks5 implements a socks5 proxy.
|
||||
package socks5
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/nadoo/glider/common/conn"
|
||||
"github.com/nadoo/glider/common/log"
|
||||
"github.com/nadoo/glider/common/socks"
|
||||
"github.com/nadoo/glider/proxy"
|
||||
)
|
||||
|
||||
// Version is socks5 version number.
|
||||
const Version = 5
|
||||
|
||||
// Socks5 is a base socks5 struct.
|
||||
type Socks5 struct {
|
||||
dialer proxy.Dialer
|
||||
proxy proxy.Proxy
|
||||
addr string
|
||||
user string
|
||||
password string
|
||||
}
|
||||
|
||||
func init() {
|
||||
proxy.RegisterDialer("socks5", NewSocks5Dialer)
|
||||
proxy.RegisterServer("socks5", NewSocks5Server)
|
||||
}
|
||||
|
||||
// NewSocks5 returns a Proxy that makes SOCKS v5 connections to the given address
|
||||
// with an optional username and password. (RFC 1928)
|
||||
func NewSocks5(s string, d proxy.Dialer, p proxy.Proxy) (*Socks5, error) {
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
log.F("parse err: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addr := u.Host
|
||||
user := u.User.Username()
|
||||
pass, _ := u.User.Password()
|
||||
|
||||
h := &Socks5{
|
||||
dialer: d,
|
||||
proxy: p,
|
||||
addr: addr,
|
||||
user: user,
|
||||
password: pass,
|
||||
}
|
||||
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// NewSocks5Dialer returns a socks5 proxy dialer.
|
||||
func NewSocks5Dialer(s string, d proxy.Dialer) (proxy.Dialer, error) {
|
||||
return NewSocks5(s, d, nil)
|
||||
}
|
||||
|
||||
// NewSocks5Server returns a socks5 proxy server.
|
||||
func NewSocks5Server(s string, p proxy.Proxy) (proxy.Server, error) {
|
||||
return NewSocks5(s, nil, p)
|
||||
}
|
||||
|
||||
// ListenAndServe serves socks5 requests.
|
||||
func (s *Socks5) ListenAndServe() {
|
||||
go s.ListenAndServeUDP()
|
||||
s.ListenAndServeTCP()
|
||||
}
|
||||
|
||||
// ListenAndServeTCP listen and serve on tcp port.
|
||||
func (s *Socks5) ListenAndServeTCP() {
|
||||
l, err := net.Listen("tcp", s.addr)
|
||||
if err != nil {
|
||||
log.F("[socks5] failed to listen on %s: %v", s.addr, err)
|
||||
return
|
||||
}
|
||||
|
||||
log.F("[socks5] listening TCP on %s", s.addr)
|
||||
|
||||
for {
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
log.F("[socks5] failed to accept: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
go s.Serve(c)
|
||||
}
|
||||
}
|
||||
|
||||
// Serve serves a connection.
|
||||
func (s *Socks5) Serve(c net.Conn) {
|
||||
defer c.Close()
|
||||
|
||||
if c, ok := c.(*net.TCPConn); ok {
|
||||
c.SetKeepAlive(true)
|
||||
}
|
||||
|
||||
tgt, err := s.handshake(c)
|
||||
if err != nil {
|
||||
// UDP: keep the connection until disconnect then free the UDP socket
|
||||
if err == socks.Errors[9] {
|
||||
buf := make([]byte, 1)
|
||||
// block here
|
||||
for {
|
||||
_, err := c.Read(buf)
|
||||
if err, ok := err.(net.Error); ok && err.Timeout() {
|
||||
continue
|
||||
}
|
||||
// log.F("[socks5] servetcp udp associate end")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
log.F("[socks5] failed to get target address: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
rc, p, err := s.proxy.Dial("tcp", tgt.String())
|
||||
if err != nil {
|
||||
log.F("[socks5] %s <-> %s via %s, error in dial: %v", c.RemoteAddr(), tgt, p, err)
|
||||
return
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
log.F("[socks5] %s <-> %s via %s", c.RemoteAddr(), tgt, p)
|
||||
|
||||
_, _, err = conn.Relay(c, rc)
|
||||
if err != nil {
|
||||
if err, ok := err.(net.Error); ok && err.Timeout() {
|
||||
return // ignore i/o timeout
|
||||
}
|
||||
log.F("[socks5] relay error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ListenAndServeUDP serves udp requests.
|
||||
func (s *Socks5) ListenAndServeUDP() {
|
||||
lc, err := net.ListenPacket("udp", s.addr)
|
||||
if err != nil {
|
||||
log.F("[socks5-udp] failed to listen on %s: %v", s.addr, err)
|
||||
return
|
||||
}
|
||||
defer lc.Close()
|
||||
|
||||
log.F("[socks5-udp] listening UDP on %s", s.addr)
|
||||
|
||||
var nm sync.Map
|
||||
buf := make([]byte, conn.UDPBufSize)
|
||||
|
||||
for {
|
||||
c := NewPktConn(lc, nil, nil, true, nil)
|
||||
|
||||
n, raddr, err := c.ReadFrom(buf)
|
||||
if err != nil {
|
||||
log.F("[socks5-udp] remote read error: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
var pc *PktConn
|
||||
v, ok := nm.Load(raddr.String())
|
||||
if !ok && v == nil {
|
||||
if c.tgtAddr == nil {
|
||||
log.F("[socks5-udp] can not get target address, not a valid request")
|
||||
continue
|
||||
}
|
||||
|
||||
lpc, nextHop, err := s.proxy.DialUDP("udp", c.tgtAddr.String())
|
||||
if err != nil {
|
||||
log.F("[socks5-udp] remote dial error: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
pc = NewPktConn(lpc, nextHop, nil, false, nil)
|
||||
nm.Store(raddr.String(), pc)
|
||||
|
||||
go func() {
|
||||
conn.RelayUDP(c, raddr, pc, 2*time.Minute)
|
||||
pc.Close()
|
||||
nm.Delete(raddr.String())
|
||||
}()
|
||||
|
||||
log.F("[socks5-udp] %s <-> %s", raddr, c.tgtAddr)
|
||||
|
||||
} else {
|
||||
pc = v.(*PktConn)
|
||||
}
|
||||
|
||||
_, err = pc.WriteTo(buf[:n], pc.writeAddr)
|
||||
if err != nil {
|
||||
log.F("[socks5-udp] remote write error: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// log.F("[socks5-udp] %s <-> %s", raddr, c.tgtAddr)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Addr returns forwarder's address.
|
||||
func (s *Socks5) Addr() string {
|
||||
if s.addr == "" {
|
||||
return s.dialer.Addr()
|
||||
}
|
||||
return s.addr
|
||||
}
|
||||
|
||||
// Dial connects to the address addr on the network net via the SOCKS5 proxy.
|
||||
func (s *Socks5) Dial(network, addr string) (net.Conn, error) {
|
||||
switch network {
|
||||
case "tcp", "tcp6", "tcp4":
|
||||
default:
|
||||
return nil, errors.New("[socks5]: no support for connection type " + network)
|
||||
}
|
||||
|
||||
c, err := s.dialer.Dial(network, s.addr)
|
||||
if err != nil {
|
||||
log.F("[socks5]: dial to %s error: %s", s.addr, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := s.connect(c, addr); err != nil {
|
||||
c.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// DialUDP connects to the given address via the proxy.
|
||||
func (s *Socks5) DialUDP(network, addr string) (pc net.PacketConn, writeTo net.Addr, err error) {
|
||||
c, err := s.dialer.Dial("tcp", s.addr)
|
||||
if err != nil {
|
||||
log.F("[socks5] dialudp dial tcp to %s error: %s", s.addr, err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// send VER, NMETHODS, METHODS
|
||||
c.Write([]byte{5, 1, 0})
|
||||
|
||||
buf := make([]byte, socks.MaxAddrLen)
|
||||
// read VER METHOD
|
||||
if _, err := io.ReadFull(c, buf[:2]); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
dstAddr := socks.ParseAddr(addr)
|
||||
// write VER CMD RSV ATYP DST.ADDR DST.PORT
|
||||
c.Write(append([]byte{5, socks.CmdUDPAssociate, 0}, dstAddr...))
|
||||
|
||||
// read VER REP RSV ATYP BND.ADDR BND.PORT
|
||||
if _, err := io.ReadFull(c, buf[:3]); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
rep := buf[1]
|
||||
if rep != 0 {
|
||||
log.F("[socks5] server reply: %d, not succeeded", rep)
|
||||
return nil, nil, errors.New("server connect failed")
|
||||
}
|
||||
|
||||
uAddr, err := socks.ReadAddrBuf(c, buf)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
pc, nextHop, err := s.dialer.DialUDP(network, uAddr.String())
|
||||
if err != nil {
|
||||
log.F("[socks5] dialudp to %s error: %s", uAddr.String(), err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
pkc := NewPktConn(pc, nextHop, dstAddr, true, c)
|
||||
return pkc, nextHop, err
|
||||
}
|
||||
|
||||
// connect takes an existing connection to a socks5 proxy server,
|
||||
// and commands the server to extend that connection to target,
|
||||
// which must be a canonical address with a host and port.
|
||||
func (s *Socks5) connect(conn net.Conn, target string) error {
|
||||
host, portStr, err := net.SplitHostPort(target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
port, err := strconv.Atoi(portStr)
|
||||
if err != nil {
|
||||
return errors.New("proxy: failed to parse port number: " + portStr)
|
||||
}
|
||||
if port < 1 || port > 0xffff {
|
||||
return errors.New("proxy: port number out of range: " + portStr)
|
||||
}
|
||||
|
||||
// the size here is just an estimate
|
||||
buf := make([]byte, 0, 6+len(host))
|
||||
|
||||
buf = append(buf, Version)
|
||||
if len(s.user) > 0 && len(s.user) < 256 && len(s.password) < 256 {
|
||||
buf = append(buf, 2 /* num auth methods */, socks.AuthNone, socks.AuthPassword)
|
||||
} else {
|
||||
buf = append(buf, 1 /* num auth methods */, socks.AuthNone)
|
||||
}
|
||||
|
||||
if _, err := conn.Write(buf); err != nil {
|
||||
return errors.New("proxy: failed to write greeting to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
|
||||
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
|
||||
return errors.New("proxy: failed to read greeting from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
if buf[0] != 5 {
|
||||
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " has unexpected version " + strconv.Itoa(int(buf[0])))
|
||||
}
|
||||
if buf[1] == 0xff {
|
||||
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " requires authentication")
|
||||
}
|
||||
|
||||
if buf[1] == socks.AuthPassword {
|
||||
buf = buf[:0]
|
||||
buf = append(buf, 1 /* password protocol version */)
|
||||
buf = append(buf, uint8(len(s.user)))
|
||||
buf = append(buf, s.user...)
|
||||
buf = append(buf, uint8(len(s.password)))
|
||||
buf = append(buf, s.password...)
|
||||
|
||||
if _, err := conn.Write(buf); err != nil {
|
||||
return errors.New("proxy: failed to write authentication request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
|
||||
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
|
||||
return errors.New("proxy: failed to read authentication reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
|
||||
if buf[1] != 0 {
|
||||
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " rejected username/password")
|
||||
}
|
||||
}
|
||||
|
||||
buf = buf[:0]
|
||||
buf = append(buf, Version, socks.CmdConnect, 0 /* reserved */)
|
||||
|
||||
if ip := net.ParseIP(host); ip != nil {
|
||||
if ip4 := ip.To4(); ip4 != nil {
|
||||
buf = append(buf, socks.ATypIP4)
|
||||
ip = ip4
|
||||
} else {
|
||||
buf = append(buf, socks.ATypIP6)
|
||||
}
|
||||
buf = append(buf, ip...)
|
||||
} else {
|
||||
if len(host) > 255 {
|
||||
return errors.New("proxy: destination hostname too long: " + host)
|
||||
}
|
||||
buf = append(buf, socks.ATypDomain)
|
||||
buf = append(buf, byte(len(host)))
|
||||
buf = append(buf, host...)
|
||||
}
|
||||
buf = append(buf, byte(port>>8), byte(port))
|
||||
|
||||
if _, err := conn.Write(buf); err != nil {
|
||||
return errors.New("proxy: failed to write connect request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
|
||||
if _, err := io.ReadFull(conn, buf[:4]); err != nil {
|
||||
return errors.New("proxy: failed to read connect reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
|
||||
failure := "unknown error"
|
||||
if int(buf[1]) < len(socks.Errors) {
|
||||
failure = socks.Errors[buf[1]].Error()
|
||||
}
|
||||
|
||||
if len(failure) > 0 {
|
||||
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " failed to connect: " + failure)
|
||||
}
|
||||
|
||||
bytesToDiscard := 0
|
||||
switch buf[3] {
|
||||
case socks.ATypIP4:
|
||||
bytesToDiscard = net.IPv4len
|
||||
case socks.ATypIP6:
|
||||
bytesToDiscard = net.IPv6len
|
||||
case socks.ATypDomain:
|
||||
_, err := io.ReadFull(conn, buf[:1])
|
||||
if err != nil {
|
||||
return errors.New("proxy: failed to read domain length from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
bytesToDiscard = int(buf[0])
|
||||
default:
|
||||
return errors.New("proxy: got unknown address type " + strconv.Itoa(int(buf[3])) + " from SOCKS5 proxy at " + s.addr)
|
||||
}
|
||||
|
||||
if cap(buf) < bytesToDiscard {
|
||||
buf = make([]byte, bytesToDiscard)
|
||||
} else {
|
||||
buf = buf[:bytesToDiscard]
|
||||
}
|
||||
if _, err := io.ReadFull(conn, buf); err != nil {
|
||||
return errors.New("proxy: failed to read address from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
|
||||
// Also need to discard the port number
|
||||
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
|
||||
return errors.New("proxy: failed to read port from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Handshake fast-tracks SOCKS initialization to get target address to connect.
|
||||
func (s *Socks5) handshake(rw io.ReadWriter) (socks.Addr, error) {
|
||||
// Read RFC 1928 for request and reply structure and sizes
|
||||
buf := make([]byte, socks.MaxAddrLen)
|
||||
// read VER, NMETHODS, METHODS
|
||||
if _, err := io.ReadFull(rw, buf[:2]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nmethods := buf[1]
|
||||
if _, err := io.ReadFull(rw, buf[:nmethods]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// write VER METHOD
|
||||
if _, err := rw.Write([]byte{5, 0}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// read VER CMD RSV ATYP DST.ADDR DST.PORT
|
||||
if _, err := io.ReadFull(rw, buf[:3]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cmd := buf[1]
|
||||
addr, err := socks.ReadAddrBuf(rw, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch cmd {
|
||||
case socks.CmdConnect:
|
||||
_, err = rw.Write([]byte{5, 0, 0, 1, 0, 0, 0, 0, 0, 0}) // SOCKS v5, reply succeeded
|
||||
case socks.CmdUDPAssociate:
|
||||
listenAddr := socks.ParseAddr(rw.(net.Conn).LocalAddr().String())
|
||||
_, err = rw.Write(append([]byte{5, 0, 0}, listenAddr...)) // SOCKS v5, reply succeeded
|
||||
if err != nil {
|
||||
return nil, socks.Errors[7]
|
||||
}
|
||||
err = socks.Errors[9]
|
||||
default:
|
||||
return nil, socks.Errors[7]
|
||||
}
|
||||
|
||||
return addr, err // skip VER, CMD, RSV fields
|
||||
}
|
67
proxy/ss/packet.go
Normal file
67
proxy/ss/packet.go
Normal file
@ -0,0 +1,67 @@
|
||||
package ss
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/nadoo/glider/common/socks"
|
||||
)
|
||||
|
||||
// PktConn .
|
||||
type PktConn struct {
|
||||
net.PacketConn
|
||||
|
||||
writeAddr net.Addr // write to and read from addr
|
||||
|
||||
tgtAddr socks.Addr
|
||||
tgtHeader bool
|
||||
}
|
||||
|
||||
// NewPktConn returns a PktConn
|
||||
func NewPktConn(c net.PacketConn, writeAddr net.Addr, tgtAddr socks.Addr, tgtHeader bool) *PktConn {
|
||||
pc := &PktConn{
|
||||
PacketConn: c,
|
||||
writeAddr: writeAddr,
|
||||
tgtAddr: tgtAddr,
|
||||
tgtHeader: tgtHeader}
|
||||
return pc
|
||||
}
|
||||
|
||||
// ReadFrom overrides the original function from net.PacketConn
|
||||
func (pc *PktConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||
if !pc.tgtHeader {
|
||||
return pc.PacketConn.ReadFrom(b)
|
||||
}
|
||||
|
||||
buf := make([]byte, len(b))
|
||||
n, raddr, err := pc.PacketConn.ReadFrom(buf)
|
||||
if err != nil {
|
||||
return n, raddr, err
|
||||
}
|
||||
|
||||
tgtAddr := socks.SplitAddr(buf)
|
||||
copy(b, buf[len(tgtAddr):])
|
||||
|
||||
//test
|
||||
if pc.writeAddr == nil {
|
||||
pc.writeAddr = raddr
|
||||
}
|
||||
|
||||
if pc.tgtAddr == nil {
|
||||
pc.tgtAddr = tgtAddr
|
||||
}
|
||||
|
||||
return n - len(tgtAddr), raddr, err
|
||||
}
|
||||
|
||||
// WriteTo overrides the original function from net.PacketConn
|
||||
func (pc *PktConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||
if !pc.tgtHeader {
|
||||
return pc.PacketConn.WriteTo(b, addr)
|
||||
}
|
||||
|
||||
buf := make([]byte, len(pc.tgtAddr)+len(b))
|
||||
copy(buf, pc.tgtAddr)
|
||||
copy(buf[len(pc.tgtAddr):], b)
|
||||
|
||||
return pc.PacketConn.WriteTo(buf, pc.writeAddr)
|
||||
}
|
280
proxy/ss/ss.go
Normal file
280
proxy/ss/ss.go
Normal file
@ -0,0 +1,280 @@
|
||||
package ss
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/nadoo/go-shadowsocks2/core"
|
||||
|
||||
"github.com/nadoo/glider/common/conn"
|
||||
"github.com/nadoo/glider/common/log"
|
||||
"github.com/nadoo/glider/common/socks"
|
||||
"github.com/nadoo/glider/proxy"
|
||||
)
|
||||
|
||||
// SS is a base ss struct.
|
||||
type SS struct {
|
||||
dialer proxy.Dialer
|
||||
proxy proxy.Proxy
|
||||
addr string
|
||||
|
||||
core.Cipher
|
||||
}
|
||||
|
||||
func init() {
|
||||
proxy.RegisterDialer("ss", NewSSDialer)
|
||||
proxy.RegisterServer("ss", NewSSServer)
|
||||
}
|
||||
|
||||
// NewSS returns a ss proxy.
|
||||
func NewSS(s string, d proxy.Dialer, p proxy.Proxy) (*SS, error) {
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
log.F("parse err: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addr := u.Host
|
||||
method := u.User.Username()
|
||||
pass, _ := u.User.Password()
|
||||
|
||||
ciph, err := core.PickCipher(method, nil, pass)
|
||||
if err != nil {
|
||||
log.Fatalf("[ss] PickCipher for '%s', error: %s", method, err)
|
||||
}
|
||||
|
||||
ss := &SS{
|
||||
dialer: d,
|
||||
proxy: p,
|
||||
addr: addr,
|
||||
Cipher: ciph,
|
||||
}
|
||||
|
||||
return ss, nil
|
||||
}
|
||||
|
||||
// NewSSDialer returns a ss proxy dialer.
|
||||
func NewSSDialer(s string, d proxy.Dialer) (proxy.Dialer, error) {
|
||||
return NewSS(s, d, nil)
|
||||
}
|
||||
|
||||
// NewSSServer returns a ss proxy server.
|
||||
func NewSSServer(s string, p proxy.Proxy) (proxy.Server, error) {
|
||||
return NewSS(s, nil, p)
|
||||
}
|
||||
|
||||
// ListenAndServe serves ss requests.
|
||||
func (s *SS) ListenAndServe() {
|
||||
go s.ListenAndServeUDP()
|
||||
s.ListenAndServeTCP()
|
||||
}
|
||||
|
||||
// ListenAndServeTCP serves tcp ss requests.
|
||||
func (s *SS) ListenAndServeTCP() {
|
||||
l, err := net.Listen("tcp", s.addr)
|
||||
if err != nil {
|
||||
log.F("[ss] failed to listen on %s: %v", s.addr, err)
|
||||
return
|
||||
}
|
||||
|
||||
log.F("[ss] listening TCP on %s", s.addr)
|
||||
|
||||
for {
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
log.F("[ss] failed to accept: %v", err)
|
||||
continue
|
||||
}
|
||||
go s.Serve(c)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Serve serves a connection.
|
||||
func (s *SS) Serve(c net.Conn) {
|
||||
defer c.Close()
|
||||
|
||||
if c, ok := c.(*net.TCPConn); ok {
|
||||
c.SetKeepAlive(true)
|
||||
}
|
||||
|
||||
c = s.StreamConn(c)
|
||||
|
||||
tgt, err := socks.ReadAddr(c)
|
||||
if err != nil {
|
||||
log.F("[ss] failed to get target address: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
dialer := s.proxy.NextDialer(tgt.String())
|
||||
|
||||
// udp over tcp?
|
||||
uot := socks.UoT(tgt[0])
|
||||
if uot && dialer.Addr() == "DIRECT" {
|
||||
rc, err := net.ListenPacket("udp", "")
|
||||
if err != nil {
|
||||
log.F("[ss-uottun] UDP remote listen error: %v", err)
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
req := make([]byte, conn.UDPBufSize)
|
||||
n, err := c.Read(req)
|
||||
if err != nil {
|
||||
log.F("[ss-uottun] error in ioutil.ReadAll: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
tgtAddr, _ := net.ResolveUDPAddr("udp", tgt.String())
|
||||
rc.WriteTo(req[:n], tgtAddr)
|
||||
|
||||
buf := make([]byte, conn.UDPBufSize)
|
||||
n, _, err = rc.ReadFrom(buf)
|
||||
if err != nil {
|
||||
log.F("[ss-uottun] read error: %v", err)
|
||||
}
|
||||
|
||||
c.Write(buf[:n])
|
||||
|
||||
log.F("[ss] %s <-tcp-> %s - %s <-udp-> %s ", c.RemoteAddr(), c.LocalAddr(), rc.LocalAddr(), tgt)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
network := "tcp"
|
||||
if uot {
|
||||
network = "udp"
|
||||
}
|
||||
|
||||
rc, err := dialer.Dial(network, tgt.String())
|
||||
if err != nil {
|
||||
log.F("[ss] %s <-> %s via %s, error in dial: %v", c.RemoteAddr(), tgt, dialer.Addr(), err)
|
||||
return
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
log.F("[ss] %s <-> %s via %s", c.RemoteAddr(), tgt, dialer.Addr())
|
||||
|
||||
_, _, err = conn.Relay(c, rc)
|
||||
if err != nil {
|
||||
if err, ok := err.(net.Error); ok && err.Timeout() {
|
||||
return // ignore i/o timeout
|
||||
}
|
||||
log.F("[ss] relay error: %v", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ListenAndServeUDP serves udp ss requests.
|
||||
func (s *SS) ListenAndServeUDP() {
|
||||
lc, err := net.ListenPacket("udp", s.addr)
|
||||
if err != nil {
|
||||
log.F("[ss-udp] failed to listen on %s: %v", s.addr, err)
|
||||
return
|
||||
}
|
||||
defer lc.Close()
|
||||
|
||||
lc = s.PacketConn(lc)
|
||||
|
||||
log.F("[ss-udp] listening UDP on %s", s.addr)
|
||||
|
||||
var nm sync.Map
|
||||
buf := make([]byte, conn.UDPBufSize)
|
||||
|
||||
for {
|
||||
c := NewPktConn(lc, nil, nil, true)
|
||||
|
||||
n, raddr, err := c.ReadFrom(buf)
|
||||
if err != nil {
|
||||
log.F("[ss-udp] remote read error: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
var pc *PktConn
|
||||
v, ok := nm.Load(raddr.String())
|
||||
if !ok && v == nil {
|
||||
lpc, nextHop, err := s.proxy.DialUDP("udp", c.tgtAddr.String())
|
||||
if err != nil {
|
||||
log.F("[ss-udp] remote dial error: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
pc = NewPktConn(lpc, nextHop, nil, false)
|
||||
nm.Store(raddr.String(), pc)
|
||||
|
||||
go func() {
|
||||
conn.RelayUDP(c, raddr, pc, 2*time.Minute)
|
||||
pc.Close()
|
||||
nm.Delete(raddr.String())
|
||||
}()
|
||||
|
||||
log.F("[ss-udp] %s <-> %s", raddr, c.tgtAddr)
|
||||
|
||||
} else {
|
||||
pc = v.(*PktConn)
|
||||
}
|
||||
|
||||
_, err = pc.WriteTo(buf[:n], pc.writeAddr)
|
||||
if err != nil {
|
||||
log.F("[ss-udp] remote write error: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// log.F("[ss-udp] %s <-> %s", raddr, c.tgtAddr)
|
||||
}
|
||||
}
|
||||
|
||||
// ListCipher returns all the ciphers supported.
|
||||
func ListCipher() string {
|
||||
return strings.Join(core.ListCipher(), " ")
|
||||
}
|
||||
|
||||
// Addr returns forwarder's address.
|
||||
func (s *SS) Addr() string {
|
||||
if s.addr == "" {
|
||||
return s.dialer.Addr()
|
||||
}
|
||||
return s.addr
|
||||
}
|
||||
|
||||
// Dial connects to the address addr on the network net via the proxy.
|
||||
func (s *SS) Dial(network, addr string) (net.Conn, error) {
|
||||
target := socks.ParseAddr(addr)
|
||||
if target == nil {
|
||||
return nil, errors.New("[ss] unable to parse address: " + addr)
|
||||
}
|
||||
|
||||
if network == "uot" {
|
||||
target[0] = target[0] | 0x8
|
||||
}
|
||||
|
||||
c, err := s.dialer.Dial("tcp", s.addr)
|
||||
if err != nil {
|
||||
log.F("[ss] dial to %s error: %s", s.addr, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c = s.StreamConn(c)
|
||||
if _, err = c.Write(target); err != nil {
|
||||
c.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c, err
|
||||
|
||||
}
|
||||
|
||||
// DialUDP connects to the given address via the proxy.
|
||||
func (s *SS) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) {
|
||||
pc, nextHop, err := s.dialer.DialUDP(network, s.addr)
|
||||
if err != nil {
|
||||
log.F("[ss] dialudp to %s error: %s", s.addr, err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
pkc := NewPktConn(s.PacketConn(pc), nextHop, socks.ParseAddr(addr), true)
|
||||
return pkc, nextHop, err
|
||||
}
|
154
proxy/ssr/ssr.go
Normal file
154
proxy/ssr/ssr.go
Normal file
@ -0,0 +1,154 @@
|
||||
package ssr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
shadowsocksr "github.com/sun8911879/shadowsocksR"
|
||||
"github.com/sun8911879/shadowsocksR/obfs"
|
||||
"github.com/sun8911879/shadowsocksR/protocol"
|
||||
"github.com/sun8911879/shadowsocksR/ssr"
|
||||
|
||||
"github.com/nadoo/glider/common/log"
|
||||
"github.com/nadoo/glider/common/socks"
|
||||
"github.com/nadoo/glider/proxy"
|
||||
)
|
||||
|
||||
// SSR struct.
|
||||
type SSR struct {
|
||||
dialer proxy.Dialer
|
||||
addr string
|
||||
|
||||
EncryptMethod string
|
||||
EncryptPassword string
|
||||
Obfs string
|
||||
ObfsParam string
|
||||
ObfsData interface{}
|
||||
Protocol string
|
||||
ProtocolParam string
|
||||
ProtocolData interface{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
proxy.RegisterDialer("ssr", NewSSRDialer)
|
||||
}
|
||||
|
||||
// NewSSR returns a shadowsocksr proxy, ssr://method:pass@host:port/query
|
||||
func NewSSR(s string, d proxy.Dialer) (*SSR, error) {
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
log.F("parse err: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addr := u.Host
|
||||
method := u.User.Username()
|
||||
pass, _ := u.User.Password()
|
||||
|
||||
p := &SSR{
|
||||
dialer: d,
|
||||
addr: addr,
|
||||
EncryptMethod: method,
|
||||
EncryptPassword: pass,
|
||||
}
|
||||
|
||||
query := u.Query()
|
||||
p.Protocol = query.Get("protocol")
|
||||
p.ProtocolParam = query.Get("protocol_param")
|
||||
p.Obfs = query.Get("obfs")
|
||||
p.ObfsParam = query.Get("obfs_param")
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// NewSSRDialer returns a ssr proxy dialer.
|
||||
func NewSSRDialer(s string, d proxy.Dialer) (proxy.Dialer, error) {
|
||||
return NewSSR(s, d)
|
||||
}
|
||||
|
||||
// Addr returns forwarder's address
|
||||
func (s *SSR) Addr() string {
|
||||
if s.addr == "" {
|
||||
return s.dialer.Addr()
|
||||
}
|
||||
return s.addr
|
||||
}
|
||||
|
||||
// Dial connects to the address addr on the network net via the proxy.
|
||||
func (s *SSR) Dial(network, addr string) (net.Conn, error) {
|
||||
target := socks.ParseAddr(addr)
|
||||
if target == nil {
|
||||
return nil, errors.New("[ssr] unable to parse address: " + addr)
|
||||
}
|
||||
|
||||
cipher, err := shadowsocksr.NewStreamCipher(s.EncryptMethod, s.EncryptPassword)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c, err := s.dialer.Dial("tcp", s.addr)
|
||||
if err != nil {
|
||||
log.F("[ssr] dial to %s error: %s", s.addr, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ssrconn := shadowsocksr.NewSSTCPConn(c, cipher)
|
||||
if ssrconn.Conn == nil || ssrconn.RemoteAddr() == nil {
|
||||
return nil, errors.New("[ssr] nil connection")
|
||||
}
|
||||
|
||||
// should initialize obfs/protocol now
|
||||
rs := strings.Split(ssrconn.RemoteAddr().String(), ":")
|
||||
port, _ := strconv.Atoi(rs[1])
|
||||
|
||||
ssrconn.IObfs = obfs.NewObfs(s.Obfs)
|
||||
if ssrconn.IObfs == nil {
|
||||
return nil, errors.New("[ssr] unsupported obfs type: " + s.Obfs)
|
||||
}
|
||||
|
||||
obfsServerInfo := &ssr.ServerInfoForObfs{
|
||||
Host: rs[0],
|
||||
Port: uint16(port),
|
||||
TcpMss: 1460,
|
||||
Param: s.ObfsParam,
|
||||
}
|
||||
ssrconn.IObfs.SetServerInfo(obfsServerInfo)
|
||||
|
||||
ssrconn.IProtocol = protocol.NewProtocol(s.Protocol)
|
||||
if ssrconn.IProtocol == nil {
|
||||
return nil, errors.New("[ssr] unsupported protocol type: " + s.Protocol)
|
||||
}
|
||||
|
||||
protocolServerInfo := &ssr.ServerInfoForObfs{
|
||||
Host: rs[0],
|
||||
Port: uint16(port),
|
||||
TcpMss: 1460,
|
||||
Param: s.ProtocolParam,
|
||||
}
|
||||
ssrconn.IProtocol.SetServerInfo(protocolServerInfo)
|
||||
|
||||
if s.ObfsData == nil {
|
||||
s.ObfsData = ssrconn.IObfs.GetData()
|
||||
}
|
||||
ssrconn.IObfs.SetData(s.ObfsData)
|
||||
|
||||
if s.ProtocolData == nil {
|
||||
s.ProtocolData = ssrconn.IProtocol.GetData()
|
||||
}
|
||||
ssrconn.IProtocol.SetData(s.ProtocolData)
|
||||
|
||||
if _, err := ssrconn.Write(target); err != nil {
|
||||
ssrconn.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ssrconn, err
|
||||
}
|
||||
|
||||
// DialUDP connects to the given address via the proxy.
|
||||
func (s *SSR) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) {
|
||||
return nil, nil, errors.New("[ssr] udp not supported now")
|
||||
}
|
95
proxy/tcptun/tcptun.go
Normal file
95
proxy/tcptun/tcptun.go
Normal file
@ -0,0 +1,95 @@
|
||||
package tcptun
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/nadoo/glider/common/conn"
|
||||
"github.com/nadoo/glider/common/log"
|
||||
"github.com/nadoo/glider/proxy"
|
||||
)
|
||||
|
||||
// TCPTun struct.
|
||||
type TCPTun struct {
|
||||
proxy proxy.Proxy
|
||||
addr string
|
||||
|
||||
raddr string
|
||||
}
|
||||
|
||||
func init() {
|
||||
proxy.RegisterServer("tcptun", NewTCPTunServer)
|
||||
}
|
||||
|
||||
// NewTCPTun returns a tcptun proxy.
|
||||
func NewTCPTun(s string, p proxy.Proxy) (*TCPTun, error) {
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
log.F("parse err: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addr := u.Host
|
||||
d := strings.Split(addr, "=")
|
||||
|
||||
t := &TCPTun{
|
||||
proxy: p,
|
||||
addr: d[0],
|
||||
raddr: d[1],
|
||||
}
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// NewTCPTunServer returns a udp tunnel server.
|
||||
func NewTCPTunServer(s string, p proxy.Proxy) (proxy.Server, error) {
|
||||
return NewTCPTun(s, p)
|
||||
}
|
||||
|
||||
// ListenAndServe listens on server's addr and serves connections.
|
||||
func (s *TCPTun) ListenAndServe() {
|
||||
l, err := net.Listen("tcp", s.addr)
|
||||
if err != nil {
|
||||
log.F("failed to listen on %s: %v", s.addr, err)
|
||||
return
|
||||
}
|
||||
|
||||
log.F("[tcptun] listening TCP on %s", s.addr)
|
||||
|
||||
for {
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
log.F("[tcptun] failed to accept: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
go s.Serve(c)
|
||||
}
|
||||
}
|
||||
|
||||
// Serve serves a connection.
|
||||
func (s *TCPTun) Serve(c net.Conn) {
|
||||
defer c.Close()
|
||||
|
||||
if c, ok := c.(*net.TCPConn); ok {
|
||||
c.SetKeepAlive(true)
|
||||
}
|
||||
|
||||
rc, p, err := s.proxy.Dial("tcp", s.raddr)
|
||||
if err != nil {
|
||||
log.F("[tcptun] %s <-> %s via %s, error in dial: %v", c.RemoteAddr(), s.addr, p, err)
|
||||
return
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
log.F("[tcptun] %s <-> %s via %s", c.RemoteAddr(), s.raddr, p)
|
||||
|
||||
_, _, err = conn.Relay(c, rc)
|
||||
if err != nil {
|
||||
if err, ok := err.(net.Error); ok && err.Timeout() {
|
||||
return // ignore i/o timeout
|
||||
}
|
||||
log.F("relay error: %v", err)
|
||||
}
|
||||
}
|
183
proxy/tls/tls.go
Normal file
183
proxy/tls/tls.go
Normal file
@ -0,0 +1,183 @@
|
||||
package tls
|
||||
|
||||
import (
|
||||
stdtls "crypto/tls"
|
||||
"errors"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/nadoo/glider/common/log"
|
||||
"github.com/nadoo/glider/proxy"
|
||||
)
|
||||
|
||||
// TLS struct.
|
||||
type TLS struct {
|
||||
dialer proxy.Dialer
|
||||
proxy proxy.Proxy
|
||||
addr string
|
||||
|
||||
tlsConfig *stdtls.Config
|
||||
|
||||
serverName string
|
||||
skipVerify bool
|
||||
|
||||
certFile string
|
||||
keyFile string
|
||||
cert stdtls.Certificate
|
||||
|
||||
server proxy.Server
|
||||
}
|
||||
|
||||
func init() {
|
||||
proxy.RegisterDialer("tls", NewTLSDialer)
|
||||
proxy.RegisterServer("tls", NewTLSServer)
|
||||
}
|
||||
|
||||
// NewTLS returns a tls proxy struct.
|
||||
func NewTLS(s string, d proxy.Dialer, p proxy.Proxy) (*TLS, error) {
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
log.F("parse url err: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addr := u.Host
|
||||
|
||||
colonPos := strings.LastIndex(addr, ":")
|
||||
if colonPos == -1 {
|
||||
colonPos = len(addr)
|
||||
}
|
||||
serverName := addr[:colonPos]
|
||||
|
||||
query := u.Query()
|
||||
skipVerify := query.Get("skipVerify")
|
||||
certFile := query.Get("cert")
|
||||
keyFile := query.Get("key")
|
||||
|
||||
t := &TLS{
|
||||
dialer: d,
|
||||
proxy: p,
|
||||
addr: addr,
|
||||
serverName: serverName,
|
||||
skipVerify: false,
|
||||
certFile: certFile,
|
||||
keyFile: keyFile,
|
||||
}
|
||||
|
||||
if skipVerify == "true" {
|
||||
t.skipVerify = true
|
||||
}
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// NewTLSDialer returns a tls proxy dialer.
|
||||
func NewTLSDialer(s string, d proxy.Dialer) (proxy.Dialer, error) {
|
||||
p, err := NewTLS(s, d, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p.tlsConfig = &stdtls.Config{
|
||||
ServerName: p.serverName,
|
||||
InsecureSkipVerify: p.skipVerify,
|
||||
ClientSessionCache: stdtls.NewLRUClientSessionCache(64),
|
||||
MinVersion: stdtls.VersionTLS10,
|
||||
}
|
||||
|
||||
return p, err
|
||||
}
|
||||
|
||||
// NewTLSServer returns a tls transport layer before the real server.
|
||||
func NewTLSServer(s string, p proxy.Proxy) (proxy.Server, error) {
|
||||
transport := strings.Split(s, ",")
|
||||
|
||||
// prepare transport listener
|
||||
// TODO: check here
|
||||
if len(transport) < 2 {
|
||||
return nil, errors.New("[tls] malformd listener:" + s)
|
||||
}
|
||||
|
||||
t, err := NewTLS(transport[0], nil, p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cert, err := stdtls.LoadX509KeyPair(t.certFile, t.keyFile)
|
||||
if err != nil {
|
||||
log.F("[tls] unable to load cert: %s, key %s", t.certFile, t.keyFile)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t.tlsConfig = &stdtls.Config{
|
||||
Certificates: []stdtls.Certificate{cert},
|
||||
MinVersion: stdtls.VersionTLS10,
|
||||
}
|
||||
|
||||
t.server, err = proxy.ServerFromURL(transport[1], p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// ListenAndServe listens on server's addr and serves connections.
|
||||
func (s *TLS) ListenAndServe() {
|
||||
l, err := net.Listen("tcp", s.addr)
|
||||
if err != nil {
|
||||
log.F("[tls] failed to listen on %s: %v", s.addr, err)
|
||||
return
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
log.F("[tls] listening TCP on %s with TLS", s.addr)
|
||||
|
||||
for {
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
log.F("[tls] failed to accept: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
go s.Serve(c)
|
||||
}
|
||||
}
|
||||
|
||||
// Serve serves a connection.
|
||||
func (s *TLS) Serve(c net.Conn) {
|
||||
// we know the internal server will close the connection after serve
|
||||
// defer c.Close()
|
||||
|
||||
if s.server != nil {
|
||||
cc := stdtls.Server(c, s.tlsConfig)
|
||||
s.server.Serve(cc)
|
||||
}
|
||||
}
|
||||
|
||||
// Addr returns forwarder's address.
|
||||
func (s *TLS) Addr() string {
|
||||
if s.addr == "" {
|
||||
return s.dialer.Addr()
|
||||
}
|
||||
return s.addr
|
||||
}
|
||||
|
||||
// Dial connects to the address addr on the network net via the proxy.
|
||||
func (s *TLS) Dial(network, addr string) (net.Conn, error) {
|
||||
cc, err := s.dialer.Dial("tcp", s.addr)
|
||||
if err != nil {
|
||||
log.F("[tls] dial to %s error: %s", s.addr, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c := stdtls.Client(cc, s.tlsConfig)
|
||||
err = c.Handshake()
|
||||
return c, err
|
||||
}
|
||||
|
||||
// DialUDP connects to the given address via the proxy.
|
||||
func (s *TLS) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) {
|
||||
return nil, nil, errors.New("tls client does not support udp now")
|
||||
}
|
177
proxy/tproxy/tproxy_linux.go
Normal file
177
proxy/tproxy/tproxy_linux.go
Normal file
@ -0,0 +1,177 @@
|
||||
// ref: https://www.kernel.org/doc/Documentation/networking/tproxy.txt
|
||||
// @LiamHaworth: https://github.com/LiamHaworth/go-tproxy/blob/master/tproxy_udp.go
|
||||
|
||||
package tproxy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/nadoo/glider/common/log"
|
||||
"github.com/nadoo/glider/proxy"
|
||||
)
|
||||
|
||||
// TProxy struct.
|
||||
type TProxy struct {
|
||||
proxy proxy.Proxy
|
||||
addr string
|
||||
}
|
||||
|
||||
func init() {
|
||||
proxy.RegisterServer("tproxy", NewTProxyServer)
|
||||
}
|
||||
|
||||
// NewTProxy returns a tproxy.
|
||||
func NewTProxy(s string, p proxy.Proxy) (*TProxy, error) {
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
log.F("parse err: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addr := u.Host
|
||||
|
||||
tp := &TProxy{
|
||||
proxy: p,
|
||||
addr: addr,
|
||||
}
|
||||
|
||||
return tp, nil
|
||||
}
|
||||
|
||||
// NewTProxyServer returns a udp tunnel server.
|
||||
func NewTProxyServer(s string, p proxy.Proxy) (proxy.Server, error) {
|
||||
return NewTProxy(s, p)
|
||||
}
|
||||
|
||||
// ListenAndServe listens on server's addr and serves connections.
|
||||
func (s *TProxy) ListenAndServe() {
|
||||
// go s.ListenAndServeTCP()
|
||||
s.ListenAndServeUDP()
|
||||
}
|
||||
|
||||
// ListenAndServeTCP .
|
||||
func (s *TProxy) ListenAndServeTCP() {
|
||||
log.F("[tproxy] tcp mode not supported now, please use 'redir' instead")
|
||||
}
|
||||
|
||||
// ListenAndServeUDP .
|
||||
func (s *TProxy) ListenAndServeUDP() {
|
||||
laddr, err := net.ResolveUDPAddr("udp", s.addr)
|
||||
if err != nil {
|
||||
log.F("[tproxy] failed to resolve addr %s: %v", s.addr, err)
|
||||
return
|
||||
}
|
||||
|
||||
lc, err := net.ListenUDP("udp", laddr)
|
||||
if err != nil {
|
||||
log.F("[tproxy] failed to listen on %s: %v", s.addr, err)
|
||||
return
|
||||
}
|
||||
|
||||
fd, err := lc.File()
|
||||
if err != nil {
|
||||
log.F("[tproxy] failed to get file descriptor: %v", err)
|
||||
return
|
||||
}
|
||||
defer fd.Close()
|
||||
|
||||
fileDescriptor := int(fd.Fd())
|
||||
if err = syscall.SetsockoptInt(fileDescriptor, syscall.SOL_IP, syscall.IP_TRANSPARENT, 1); err != nil {
|
||||
syscall.Close(fileDescriptor)
|
||||
log.F("[tproxy] failed to set socket option IP_TRANSPARENT: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = syscall.SetsockoptInt(fileDescriptor, syscall.SOL_IP, syscall.IP_RECVORIGDSTADDR, 1); err != nil {
|
||||
syscall.Close(fileDescriptor)
|
||||
log.F("[tproxy] failed to set socket option IP_RECVORIGDSTADDR: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
buf := make([]byte, 1024)
|
||||
_, srcAddr, dstAddr, err := ReadFromUDP(lc, buf)
|
||||
if err != nil {
|
||||
if netErr, ok := err.(net.Error); ok && netErr.Temporary() {
|
||||
log.F("[tproxy] temporary reading data error: %s", netErr)
|
||||
continue
|
||||
}
|
||||
|
||||
log.F("[tproxy] Unrecoverable error while reading data: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
log.F("[tproxy] Accepting UDP connection from %s with destination of %s", srcAddr.String(), dstAddr.String())
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Serve .
|
||||
func (s *TProxy) Serve(c net.Conn) {
|
||||
log.F("[tproxy] func Serve: can not be called directly")
|
||||
}
|
||||
|
||||
// ReadFromUDP reads a UDP packet from c, copying the payload into b.
|
||||
// It returns the number of bytes copied into b and the return address
|
||||
// that was on the packet.
|
||||
//
|
||||
// Out-of-band data is also read in so that the original destination
|
||||
// address can be identified and parsed.
|
||||
func ReadFromUDP(conn *net.UDPConn, b []byte) (int, *net.UDPAddr, *net.UDPAddr, error) {
|
||||
oob := make([]byte, 1024)
|
||||
n, oobn, _, addr, err := conn.ReadMsgUDP(b, oob)
|
||||
if err != nil {
|
||||
return 0, nil, nil, err
|
||||
}
|
||||
|
||||
msgs, err := syscall.ParseSocketControlMessage(oob[:oobn])
|
||||
if err != nil {
|
||||
return 0, nil, nil, fmt.Errorf("parsing socket control message: %s", err)
|
||||
}
|
||||
|
||||
var originalDst *net.UDPAddr
|
||||
for _, msg := range msgs {
|
||||
if msg.Header.Level == syscall.SOL_IP && msg.Header.Type == syscall.IP_RECVORIGDSTADDR {
|
||||
originalDstRaw := &syscall.RawSockaddrInet4{}
|
||||
if err = binary.Read(bytes.NewReader(msg.Data), binary.LittleEndian, originalDstRaw); err != nil {
|
||||
return 0, nil, nil, fmt.Errorf("reading original destination address: %s", err)
|
||||
}
|
||||
|
||||
switch originalDstRaw.Family {
|
||||
case syscall.AF_INET:
|
||||
pp := (*syscall.RawSockaddrInet4)(unsafe.Pointer(originalDstRaw))
|
||||
p := (*[2]byte)(unsafe.Pointer(&pp.Port))
|
||||
originalDst = &net.UDPAddr{
|
||||
IP: net.IPv4(pp.Addr[0], pp.Addr[1], pp.Addr[2], pp.Addr[3]),
|
||||
Port: int(p[0])<<8 + int(p[1]),
|
||||
}
|
||||
|
||||
case syscall.AF_INET6:
|
||||
pp := (*syscall.RawSockaddrInet6)(unsafe.Pointer(originalDstRaw))
|
||||
p := (*[2]byte)(unsafe.Pointer(&pp.Port))
|
||||
originalDst = &net.UDPAddr{
|
||||
IP: net.IP(pp.Addr[:]),
|
||||
Port: int(p[0])<<8 + int(p[1]),
|
||||
Zone: strconv.Itoa(int(pp.Scope_id)),
|
||||
}
|
||||
|
||||
default:
|
||||
return 0, nil, nil, fmt.Errorf("original destination is an unsupported network family")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if originalDst == nil {
|
||||
return 0, nil, nil, fmt.Errorf("unable to obtain original destination: %s", err)
|
||||
}
|
||||
|
||||
return n, addr, originalDst, nil
|
||||
}
|
110
proxy/udptun/udptun.go
Normal file
110
proxy/udptun/udptun.go
Normal file
@ -0,0 +1,110 @@
|
||||
package udptun
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/nadoo/glider/common/conn"
|
||||
"github.com/nadoo/glider/common/log"
|
||||
"github.com/nadoo/glider/proxy"
|
||||
)
|
||||
|
||||
// UDPTun is a base udptun struct.
|
||||
type UDPTun struct {
|
||||
proxy proxy.Proxy
|
||||
addr string
|
||||
taddr string // tunnel addr string
|
||||
tuaddr *net.UDPAddr // tunnel addr
|
||||
}
|
||||
|
||||
func init() {
|
||||
proxy.RegisterServer("udptun", NewUDPTunServer)
|
||||
}
|
||||
|
||||
// NewUDPTun returns a UDPTun proxy.
|
||||
func NewUDPTun(s string, p proxy.Proxy) (*UDPTun, error) {
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
log.F("[udptun] parse err: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addr := u.Host
|
||||
d := strings.Split(addr, "=")
|
||||
|
||||
ut := &UDPTun{
|
||||
proxy: p,
|
||||
addr: d[0],
|
||||
taddr: d[1],
|
||||
}
|
||||
|
||||
ut.tuaddr, err = net.ResolveUDPAddr("udp", ut.taddr)
|
||||
return ut, err
|
||||
}
|
||||
|
||||
// NewUDPTunServer returns a udp tunnel server.
|
||||
func NewUDPTunServer(s string, p proxy.Proxy) (proxy.Server, error) {
|
||||
return NewUDPTun(s, p)
|
||||
}
|
||||
|
||||
// ListenAndServe listen and serves on the given address.
|
||||
func (s *UDPTun) ListenAndServe() {
|
||||
c, err := net.ListenPacket("udp", s.addr)
|
||||
if err != nil {
|
||||
log.F("[udptun] failed to listen on %s: %v", s.addr, err)
|
||||
return
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
log.F("[udptun] listening UDP on %s", s.addr)
|
||||
|
||||
var nm sync.Map
|
||||
buf := make([]byte, conn.UDPBufSize)
|
||||
|
||||
for {
|
||||
n, raddr, err := c.ReadFrom(buf)
|
||||
if err != nil {
|
||||
log.F("[udptun] read error: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
var pc net.PacketConn
|
||||
|
||||
v, ok := nm.Load(raddr.String())
|
||||
if !ok && v == nil {
|
||||
pc, _, err = s.proxy.DialUDP("udp", s.taddr)
|
||||
if err != nil {
|
||||
log.F("[udptun] remote dial error: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
nm.Store(raddr.String(), pc)
|
||||
|
||||
go func() {
|
||||
conn.RelayUDP(c, raddr, pc, 2*time.Minute)
|
||||
pc.Close()
|
||||
nm.Delete(raddr.String())
|
||||
}()
|
||||
|
||||
} else {
|
||||
pc = v.(net.PacketConn)
|
||||
}
|
||||
|
||||
_, err = pc.WriteTo(buf[:n], s.tuaddr)
|
||||
if err != nil {
|
||||
log.F("[udptun] remote write error: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
log.F("[udptun] %s <-> %s", raddr, s.taddr)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Serve serves a net.Conn, can not be called directly.
|
||||
func (s *UDPTun) Serve(c net.Conn) {
|
||||
log.F("[udptun] func Serve: can not be called directly")
|
||||
}
|
123
proxy/unix/unix.go
Normal file
123
proxy/unix/unix.go
Normal file
@ -0,0 +1,123 @@
|
||||
package unix
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/nadoo/glider/common/log"
|
||||
"github.com/nadoo/glider/proxy"
|
||||
)
|
||||
|
||||
// Unix domain socket struct.
|
||||
type Unix struct {
|
||||
dialer proxy.Dialer
|
||||
proxy proxy.Proxy
|
||||
addr string
|
||||
|
||||
server proxy.Server
|
||||
}
|
||||
|
||||
func init() {
|
||||
proxy.RegisterServer("unix", NewUnixServer)
|
||||
proxy.RegisterDialer("unix", NewUnixDialer)
|
||||
}
|
||||
|
||||
// NewUnix returns unix fomain socket proxy.
|
||||
func NewUnix(s string, d proxy.Dialer, p proxy.Proxy) (*Unix, error) {
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
log.F("parse url err: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
unix := &Unix{
|
||||
dialer: d,
|
||||
proxy: p,
|
||||
addr: u.Path,
|
||||
}
|
||||
|
||||
return unix, nil
|
||||
}
|
||||
|
||||
// NewUnixDialer returns a unix domain socket dialer.
|
||||
func NewUnixDialer(s string, d proxy.Dialer) (proxy.Dialer, error) {
|
||||
return NewUnix(s, d, nil)
|
||||
}
|
||||
|
||||
// NewUnixServer returns a unix domain socket server.
|
||||
func NewUnixServer(s string, p proxy.Proxy) (proxy.Server, error) {
|
||||
transport := strings.Split(s, ",")
|
||||
|
||||
// prepare transport listener
|
||||
// TODO: check here
|
||||
if len(transport) < 2 {
|
||||
return nil, errors.New("[unix] malformd listener:" + s)
|
||||
}
|
||||
|
||||
unix, err := NewUnix(transport[0], nil, p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
unix.server, err = proxy.ServerFromURL(transport[1], p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return unix, nil
|
||||
}
|
||||
|
||||
// ListenAndServe serves requests.
|
||||
func (s *Unix) ListenAndServe() {
|
||||
os.Remove(s.addr)
|
||||
l, err := net.Listen("unix", s.addr)
|
||||
if err != nil {
|
||||
log.F("[unix] failed to listen on %s: %v", s.addr, err)
|
||||
return
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
log.F("[unix] listening on %s", s.addr)
|
||||
|
||||
for {
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
log.F("[unix] failed to accept: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
go s.Serve(c)
|
||||
}
|
||||
}
|
||||
|
||||
// Serve serves requests.
|
||||
func (s *Unix) Serve(c net.Conn) {
|
||||
// we know the internal server will close the connection after serve
|
||||
// defer c.Close()
|
||||
|
||||
if s.server != nil {
|
||||
s.server.Serve(c)
|
||||
}
|
||||
}
|
||||
|
||||
// Addr returns forwarder's address.
|
||||
func (s *Unix) Addr() string {
|
||||
if s.addr == "" {
|
||||
return s.dialer.Addr()
|
||||
}
|
||||
return s.addr
|
||||
}
|
||||
|
||||
// Dial connects to the address addr on the network net via the proxy.
|
||||
func (s *Unix) Dial(network, addr string) (net.Conn, error) {
|
||||
// NOTE: must be the first dialer in a chain
|
||||
return net.Dial("unix", s.addr)
|
||||
}
|
||||
|
||||
// DialUDP connects to the given address via the proxy.
|
||||
func (s *Unix) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) {
|
||||
return nil, nil, errors.New("unix domain socket client does not support udp now")
|
||||
}
|
110
proxy/uottun/uottun.go
Normal file
110
proxy/uottun/uottun.go
Normal file
@ -0,0 +1,110 @@
|
||||
package uottun
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/nadoo/glider/common/conn"
|
||||
"github.com/nadoo/glider/common/log"
|
||||
"github.com/nadoo/glider/proxy"
|
||||
)
|
||||
|
||||
// UoTTun is a base udp over tcp tunnel struct.
|
||||
type UoTTun struct {
|
||||
proxy proxy.Proxy
|
||||
addr string
|
||||
|
||||
raddr string
|
||||
}
|
||||
|
||||
func init() {
|
||||
proxy.RegisterServer("uottun", NewUoTTunServer)
|
||||
}
|
||||
|
||||
// NewUoTTun returns a UoTTun proxy.
|
||||
func NewUoTTun(s string, p proxy.Proxy) (*UoTTun, error) {
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
log.F("parse err: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addr := u.Host
|
||||
d := strings.Split(addr, "=")
|
||||
|
||||
ut := &UoTTun{
|
||||
proxy: p,
|
||||
addr: d[0],
|
||||
raddr: d[1],
|
||||
}
|
||||
|
||||
return ut, nil
|
||||
}
|
||||
|
||||
// NewUoTTunServer returns a uot tunnel server.
|
||||
func NewUoTTunServer(s string, p proxy.Proxy) (proxy.Server, error) {
|
||||
return NewUoTTun(s, p)
|
||||
}
|
||||
|
||||
// ListenAndServe listen and serve on tcp.
|
||||
func (s *UoTTun) ListenAndServe() {
|
||||
c, err := net.ListenPacket("udp", s.addr)
|
||||
if err != nil {
|
||||
log.F("[uottun] failed to listen on %s: %v", s.addr, err)
|
||||
return
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
log.F("[uottun] listening UDP on %s", s.addr)
|
||||
|
||||
buf := make([]byte, conn.UDPBufSize)
|
||||
|
||||
for {
|
||||
n, clientAddr, err := c.ReadFrom(buf)
|
||||
if err != nil {
|
||||
log.F("[uottun] read error: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
rc, p, err := s.proxy.Dial("uot", s.raddr)
|
||||
if err != nil {
|
||||
log.F("[uottun] failed to connect to server %v: %v", s.raddr, err)
|
||||
continue
|
||||
}
|
||||
|
||||
go func() {
|
||||
// no remote forwarder, just a local udp forwarder
|
||||
if urc, ok := rc.(*net.UDPConn); ok {
|
||||
conn.RelayUDP(c, clientAddr, urc, 2*time.Minute)
|
||||
urc.Close()
|
||||
return
|
||||
}
|
||||
|
||||
// remote forwarder, udp over tcp
|
||||
resp, err := ioutil.ReadAll(rc)
|
||||
if err != nil {
|
||||
log.F("error in ioutil.ReadAll: %s\n", err)
|
||||
return
|
||||
}
|
||||
rc.Close()
|
||||
c.WriteTo(resp, clientAddr)
|
||||
}()
|
||||
|
||||
_, err = rc.Write(buf[:n])
|
||||
if err != nil {
|
||||
log.F("[uottun] remote write error: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
log.F("[uottun] %s <-> %s via %s", clientAddr, s.raddr, p)
|
||||
}
|
||||
}
|
||||
|
||||
// Serve is not allowed to be called directly.
|
||||
func (s *UoTTun) Serve(c net.Conn) {
|
||||
// TODO
|
||||
log.F("[uottun] func Serve: can not be called directly")
|
||||
}
|
61
proxy/vmess/addr.go
Normal file
61
proxy/vmess/addr.go
Normal file
@ -0,0 +1,61 @@
|
||||
package vmess
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Atyp is vmess addr type
|
||||
type Atyp byte
|
||||
|
||||
// Atyp
|
||||
const (
|
||||
AtypErr Atyp = 0
|
||||
AtypIP4 Atyp = 1
|
||||
AtypDomain Atyp = 2
|
||||
AtypIP6 Atyp = 3
|
||||
)
|
||||
|
||||
// Addr is vmess addr
|
||||
type Addr []byte
|
||||
|
||||
// Port is vmess addr port
|
||||
type Port uint16
|
||||
|
||||
// ParseAddr parses the address in string s
|
||||
func ParseAddr(s string) (Atyp, Addr, Port, error) {
|
||||
var atyp Atyp
|
||||
var addr Addr
|
||||
|
||||
host, port, err := net.SplitHostPort(s)
|
||||
if err != nil {
|
||||
return 0, nil, 0, err
|
||||
}
|
||||
|
||||
if ip := net.ParseIP(host); ip != nil {
|
||||
if ip4 := ip.To4(); ip4 != nil {
|
||||
addr = make([]byte, net.IPv4len)
|
||||
atyp = AtypIP4
|
||||
copy(addr[:], ip4)
|
||||
} else {
|
||||
addr = make([]byte, net.IPv6len)
|
||||
atyp = AtypIP6
|
||||
copy(addr[:], ip)
|
||||
}
|
||||
} else {
|
||||
if len(host) > 255 {
|
||||
return 0, nil, 0, err
|
||||
}
|
||||
addr = make([]byte, 1+len(host))
|
||||
atyp = AtypDomain
|
||||
addr[0] = byte(len(host))
|
||||
copy(addr[1:], host)
|
||||
}
|
||||
|
||||
portnum, err := strconv.ParseUint(port, 10, 16)
|
||||
if err != nil {
|
||||
return 0, nil, 0, err
|
||||
}
|
||||
|
||||
return atyp, addr, Port(portnum), err
|
||||
}
|
136
proxy/vmess/aead.go
Normal file
136
proxy/vmess/aead.go
Normal file
@ -0,0 +1,136 @@
|
||||
package vmess
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/cipher"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
)
|
||||
|
||||
type aeadWriter struct {
|
||||
io.Writer
|
||||
cipher.AEAD
|
||||
nonce []byte
|
||||
buf []byte
|
||||
count uint16
|
||||
iv []byte
|
||||
}
|
||||
|
||||
// AEADWriter returns a aead writer
|
||||
func AEADWriter(w io.Writer, aead cipher.AEAD, iv []byte) io.Writer {
|
||||
return &aeadWriter{
|
||||
Writer: w,
|
||||
AEAD: aead,
|
||||
buf: make([]byte, lenSize+maxChunkSize),
|
||||
nonce: make([]byte, aead.NonceSize()),
|
||||
count: 0,
|
||||
iv: iv,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *aeadWriter) Write(b []byte) (int, error) {
|
||||
n, err := w.ReadFrom(bytes.NewBuffer(b))
|
||||
return int(n), err
|
||||
}
|
||||
|
||||
func (w *aeadWriter) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
for {
|
||||
buf := w.buf
|
||||
payloadBuf := buf[lenSize : lenSize+defaultChunkSize-w.Overhead()]
|
||||
|
||||
nr, er := r.Read(payloadBuf)
|
||||
if nr > 0 {
|
||||
n += int64(nr)
|
||||
buf = buf[:lenSize+nr+w.Overhead()]
|
||||
payloadBuf = payloadBuf[:nr]
|
||||
binary.BigEndian.PutUint16(buf[:lenSize], uint16(nr+w.Overhead()))
|
||||
|
||||
binary.BigEndian.PutUint16(w.nonce[:2], w.count)
|
||||
copy(w.nonce[2:], w.iv[2:12])
|
||||
|
||||
w.Seal(payloadBuf[:0], w.nonce, payloadBuf, nil)
|
||||
w.count++
|
||||
|
||||
_, ew := w.Writer.Write(buf)
|
||||
if ew != nil {
|
||||
err = ew
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if er != nil {
|
||||
if er != io.EOF { // ignore EOF as per io.ReaderFrom contract
|
||||
err = er
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
type aeadReader struct {
|
||||
io.Reader
|
||||
cipher.AEAD
|
||||
nonce []byte
|
||||
buf []byte
|
||||
leftover []byte
|
||||
count uint16
|
||||
iv []byte
|
||||
}
|
||||
|
||||
// AEADReader returns a aead reader
|
||||
func AEADReader(r io.Reader, aead cipher.AEAD, iv []byte) io.Reader {
|
||||
return &aeadReader{
|
||||
Reader: r,
|
||||
AEAD: aead,
|
||||
buf: make([]byte, lenSize+maxChunkSize),
|
||||
nonce: make([]byte, aead.NonceSize()),
|
||||
count: 0,
|
||||
iv: iv,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *aeadReader) Read(b []byte) (int, error) {
|
||||
if len(r.leftover) > 0 {
|
||||
n := copy(b, r.leftover)
|
||||
r.leftover = r.leftover[n:]
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// get length
|
||||
_, err := io.ReadFull(r.Reader, r.buf[:lenSize])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// if length == 0, then this is the end
|
||||
l := binary.BigEndian.Uint16(r.buf[:lenSize])
|
||||
if l == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// get payload
|
||||
buf := r.buf[:l]
|
||||
_, err = io.ReadFull(r.Reader, buf)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
binary.BigEndian.PutUint16(r.nonce[:2], r.count)
|
||||
copy(r.nonce[2:], r.iv[2:12])
|
||||
|
||||
_, err = r.Open(buf[:0], r.nonce, buf, nil)
|
||||
r.count++
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
dataLen := int(l) - r.Overhead()
|
||||
m := copy(b, r.buf[:dataLen])
|
||||
if m < int(dataLen) {
|
||||
r.leftover = r.buf[m:dataLen]
|
||||
}
|
||||
|
||||
return m, err
|
||||
}
|
104
proxy/vmess/chunk.go
Normal file
104
proxy/vmess/chunk.go
Normal file
@ -0,0 +1,104 @@
|
||||
package vmess
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
)
|
||||
|
||||
const (
|
||||
lenSize = 2
|
||||
maxChunkSize = 1 << 14 // 16384
|
||||
defaultChunkSize = 1 << 13 // 8192
|
||||
)
|
||||
|
||||
type chunkedWriter struct {
|
||||
io.Writer
|
||||
buf []byte
|
||||
}
|
||||
|
||||
// ChunkedWriter returns a chunked writer
|
||||
func ChunkedWriter(w io.Writer) io.Writer {
|
||||
return &chunkedWriter{
|
||||
Writer: w,
|
||||
buf: make([]byte, lenSize+maxChunkSize),
|
||||
}
|
||||
}
|
||||
|
||||
func (w *chunkedWriter) Write(b []byte) (int, error) {
|
||||
n, err := w.ReadFrom(bytes.NewBuffer(b))
|
||||
return int(n), err
|
||||
}
|
||||
|
||||
func (w *chunkedWriter) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
for {
|
||||
buf := w.buf
|
||||
payloadBuf := buf[lenSize : lenSize+defaultChunkSize]
|
||||
|
||||
nr, er := r.Read(payloadBuf)
|
||||
if nr > 0 {
|
||||
n += int64(nr)
|
||||
buf = buf[:lenSize+nr]
|
||||
payloadBuf = payloadBuf[:nr]
|
||||
binary.BigEndian.PutUint16(buf[:lenSize], uint16(nr))
|
||||
|
||||
_, ew := w.Writer.Write(buf)
|
||||
if ew != nil {
|
||||
err = ew
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if er != nil {
|
||||
if er != io.EOF { // ignore EOF as per io.ReaderFrom contract
|
||||
err = er
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
type chunkedReader struct {
|
||||
io.Reader
|
||||
buf []byte
|
||||
leftBytes int
|
||||
}
|
||||
|
||||
// ChunkedReader returns a chunked reader
|
||||
func ChunkedReader(r io.Reader) io.Reader {
|
||||
return &chunkedReader{
|
||||
Reader: r,
|
||||
buf: make([]byte, lenSize), // NOTE: buf only used to save header bytes now
|
||||
}
|
||||
}
|
||||
|
||||
func (r *chunkedReader) Read(b []byte) (int, error) {
|
||||
if r.leftBytes == 0 {
|
||||
// get length
|
||||
_, err := io.ReadFull(r.Reader, r.buf[:lenSize])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
r.leftBytes = int(binary.BigEndian.Uint16(r.buf[:lenSize]))
|
||||
|
||||
// if length == 0, then this is the end
|
||||
if r.leftBytes == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
}
|
||||
|
||||
readLen := len(b)
|
||||
if readLen > r.leftBytes {
|
||||
readLen = r.leftBytes
|
||||
}
|
||||
|
||||
m, err := r.Reader.Read(b[:readLen])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
r.leftBytes -= m
|
||||
return m, err
|
||||
}
|
308
proxy/vmess/client.go
Normal file
308
proxy/vmess/client.go
Normal file
@ -0,0 +1,308 @@
|
||||
package vmess
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/hmac"
|
||||
"crypto/md5"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"hash/fnv"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
)
|
||||
|
||||
// Request Options
|
||||
const (
|
||||
OptBasicFormat byte = 0
|
||||
OptChunkStream byte = 1
|
||||
// OptReuseTCPConnection byte = 2
|
||||
// OptMetadataObfuscate byte = 4
|
||||
)
|
||||
|
||||
// Security types
|
||||
const (
|
||||
SecurityAES128GCM byte = 3
|
||||
SecurityChacha20Poly1305 byte = 4
|
||||
SecurityNone byte = 5
|
||||
)
|
||||
|
||||
// CMD types
|
||||
const (
|
||||
CmdTCP byte = 1
|
||||
CmdUDP byte = 2
|
||||
)
|
||||
|
||||
// Client vmess client
|
||||
type Client struct {
|
||||
users []*User
|
||||
count int
|
||||
opt byte
|
||||
security byte
|
||||
}
|
||||
|
||||
// Conn is a connection to vmess server
|
||||
type Conn struct {
|
||||
user *User
|
||||
opt byte
|
||||
security byte
|
||||
|
||||
atyp Atyp
|
||||
addr Addr
|
||||
port Port
|
||||
|
||||
reqBodyIV [16]byte
|
||||
reqBodyKey [16]byte
|
||||
reqRespV byte
|
||||
respBodyIV [16]byte
|
||||
respBodyKey [16]byte
|
||||
|
||||
net.Conn
|
||||
dataReader io.Reader
|
||||
dataWriter io.Writer
|
||||
}
|
||||
|
||||
// NewClient .
|
||||
func NewClient(uuidStr, security string, alterID int) (*Client, error) {
|
||||
uuid, err := StrToUUID(uuidStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c := &Client{}
|
||||
user := NewUser(uuid)
|
||||
c.users = append(c.users, user)
|
||||
c.users = append(c.users, user.GenAlterIDUsers(alterID)...)
|
||||
c.count = len(c.users)
|
||||
|
||||
c.opt = OptChunkStream
|
||||
|
||||
security = strings.ToLower(security)
|
||||
switch security {
|
||||
case "aes-128-gcm":
|
||||
c.security = SecurityAES128GCM
|
||||
case "chacha20-poly1305":
|
||||
c.security = SecurityChacha20Poly1305
|
||||
case "none":
|
||||
c.security = SecurityNone
|
||||
case "":
|
||||
// NOTE: use basic format when no method specified
|
||||
c.opt = OptBasicFormat
|
||||
c.security = SecurityNone
|
||||
default:
|
||||
return nil, errors.New("unknown security type: " + security)
|
||||
}
|
||||
|
||||
// NOTE: give rand a new seed to avoid the same sequence of values
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// NewConn .
|
||||
func (c *Client) NewConn(rc net.Conn, target string) (*Conn, error) {
|
||||
r := rand.Intn(c.count)
|
||||
conn := &Conn{user: c.users[r], opt: c.opt, security: c.security}
|
||||
|
||||
var err error
|
||||
conn.atyp, conn.addr, conn.port, err = ParseAddr(target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
randBytes := make([]byte, 33)
|
||||
rand.Read(randBytes)
|
||||
|
||||
copy(conn.reqBodyIV[:], randBytes[:16])
|
||||
copy(conn.reqBodyKey[:], randBytes[16:32])
|
||||
conn.reqRespV = randBytes[32]
|
||||
|
||||
conn.respBodyIV = md5.Sum(conn.reqBodyIV[:])
|
||||
conn.respBodyKey = md5.Sum(conn.reqBodyKey[:])
|
||||
|
||||
// AuthInfo
|
||||
_, err = rc.Write(conn.EncodeAuthInfo())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Request
|
||||
req, err := conn.EncodeRequest()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = rc.Write(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conn.Conn = rc
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// EncodeAuthInfo returns HMAC("md5", UUID, UTC) result
|
||||
func (c *Conn) EncodeAuthInfo() []byte {
|
||||
ts := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(ts, uint64(time.Now().UTC().Unix()))
|
||||
|
||||
h := hmac.New(md5.New, c.user.UUID[:])
|
||||
h.Write(ts)
|
||||
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
// EncodeRequest encodes requests to network bytes
|
||||
func (c *Conn) EncodeRequest() ([]byte, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
// Request
|
||||
buf.WriteByte(1) // Ver
|
||||
buf.Write(c.reqBodyIV[:]) // IV
|
||||
buf.Write(c.reqBodyKey[:]) // Key
|
||||
buf.WriteByte(c.reqRespV) // V
|
||||
buf.WriteByte(c.opt) // Opt
|
||||
|
||||
// pLen and Sec
|
||||
paddingLen := rand.Intn(16)
|
||||
pSec := byte(paddingLen<<4) | c.security // P(4bit) and Sec(4bit)
|
||||
buf.WriteByte(pSec)
|
||||
|
||||
buf.WriteByte(0) // reserved
|
||||
buf.WriteByte(CmdTCP) // cmd
|
||||
|
||||
// target
|
||||
err := binary.Write(buf, binary.BigEndian, uint16(c.port)) // port
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf.WriteByte(byte(c.atyp)) // atyp
|
||||
buf.Write(c.addr) // addr
|
||||
|
||||
// padding
|
||||
if paddingLen > 0 {
|
||||
padding := make([]byte, paddingLen)
|
||||
rand.Read(padding)
|
||||
buf.Write(padding)
|
||||
}
|
||||
|
||||
// F
|
||||
fnv1a := fnv.New32a()
|
||||
_, err = fnv1a.Write(buf.Bytes())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf.Write(fnv1a.Sum(nil))
|
||||
|
||||
block, err := aes.NewCipher(c.user.CmdKey[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stream := cipher.NewCFBEncrypter(block, TimestampHash(time.Now().UTC()))
|
||||
stream.XORKeyStream(buf.Bytes(), buf.Bytes())
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// DecodeRespHeader .
|
||||
func (c *Conn) DecodeRespHeader() error {
|
||||
block, err := aes.NewCipher(c.respBodyKey[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stream := cipher.NewCFBDecrypter(block, c.respBodyIV[:])
|
||||
|
||||
buf := make([]byte, 4)
|
||||
_, err = io.ReadFull(c.Conn, buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stream.XORKeyStream(buf, buf)
|
||||
|
||||
if buf[0] != c.reqRespV {
|
||||
return errors.New("unexpected response header")
|
||||
}
|
||||
|
||||
// TODO: Dynamic port support
|
||||
if buf[2] != 0 {
|
||||
// dataLen := int32(buf[3])
|
||||
return errors.New("dynamic port is not supported now")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conn) Write(b []byte) (n int, err error) {
|
||||
if c.dataWriter != nil {
|
||||
return c.dataWriter.Write(b)
|
||||
}
|
||||
|
||||
c.dataWriter = c.Conn
|
||||
if c.opt&OptChunkStream == OptChunkStream {
|
||||
switch c.security {
|
||||
case SecurityNone:
|
||||
c.dataWriter = ChunkedWriter(c.Conn)
|
||||
|
||||
case SecurityAES128GCM:
|
||||
block, _ := aes.NewCipher(c.reqBodyKey[:])
|
||||
aead, _ := cipher.NewGCM(block)
|
||||
c.dataWriter = AEADWriter(c.Conn, aead, c.reqBodyIV[:])
|
||||
|
||||
case SecurityChacha20Poly1305:
|
||||
key := make([]byte, 32)
|
||||
t := md5.Sum(c.reqBodyKey[:])
|
||||
copy(key, t[:])
|
||||
t = md5.Sum(key[:16])
|
||||
copy(key[16:], t[:])
|
||||
aead, _ := chacha20poly1305.New(key)
|
||||
c.dataWriter = AEADWriter(c.Conn, aead, c.reqBodyIV[:])
|
||||
}
|
||||
}
|
||||
|
||||
return c.dataWriter.Write(b)
|
||||
}
|
||||
|
||||
func (c *Conn) Read(b []byte) (n int, err error) {
|
||||
if c.dataReader != nil {
|
||||
return c.dataReader.Read(b)
|
||||
}
|
||||
|
||||
err = c.DecodeRespHeader()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
c.dataReader = c.Conn
|
||||
if c.opt&OptChunkStream == OptChunkStream {
|
||||
switch c.security {
|
||||
case SecurityNone:
|
||||
c.dataReader = ChunkedReader(c.Conn)
|
||||
|
||||
case SecurityAES128GCM:
|
||||
block, _ := aes.NewCipher(c.respBodyKey[:])
|
||||
aead, _ := cipher.NewGCM(block)
|
||||
c.dataReader = AEADReader(c.Conn, aead, c.respBodyIV[:])
|
||||
|
||||
case SecurityChacha20Poly1305:
|
||||
key := make([]byte, 32)
|
||||
t := md5.Sum(c.respBodyKey[:])
|
||||
copy(key, t[:])
|
||||
t = md5.Sum(key[:16])
|
||||
copy(key[16:], t[:])
|
||||
aead, _ := chacha20poly1305.New(key)
|
||||
c.dataReader = AEADReader(c.Conn, aead, c.respBodyIV[:])
|
||||
}
|
||||
}
|
||||
|
||||
return c.dataReader.Read(b)
|
||||
}
|
85
proxy/vmess/user.go
Normal file
85
proxy/vmess/user.go
Normal file
@ -0,0 +1,85 @@
|
||||
package vmess
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// User of vmess client
|
||||
type User struct {
|
||||
UUID [16]byte
|
||||
CmdKey [16]byte
|
||||
}
|
||||
|
||||
// NewUser .
|
||||
func NewUser(uuid [16]byte) *User {
|
||||
u := &User{UUID: uuid}
|
||||
copy(u.CmdKey[:], GetKey(uuid))
|
||||
return u
|
||||
}
|
||||
|
||||
func nextID(oldID [16]byte) (newID [16]byte) {
|
||||
md5hash := md5.New()
|
||||
md5hash.Write(oldID[:])
|
||||
md5hash.Write([]byte("16167dc8-16b6-4e6d-b8bb-65dd68113a81"))
|
||||
for {
|
||||
md5hash.Sum(newID[:0])
|
||||
if !bytes.Equal(oldID[:], newID[:]) {
|
||||
return
|
||||
}
|
||||
md5hash.Write([]byte("533eff8a-4113-4b10-b5ce-0f5d76b98cd2"))
|
||||
}
|
||||
}
|
||||
|
||||
// GenAlterIDUsers generates users according to primary user's id and alterID
|
||||
func (u *User) GenAlterIDUsers(alterID int) []*User {
|
||||
users := make([]*User, alterID)
|
||||
preID := u.UUID
|
||||
for i := 0; i < alterID; i++ {
|
||||
newID := nextID(preID)
|
||||
// NOTE: alterID user is a user which have a different uuid but a same cmdkey with the primary user.
|
||||
users[i] = &User{UUID: newID, CmdKey: u.CmdKey}
|
||||
preID = newID
|
||||
}
|
||||
|
||||
return users
|
||||
}
|
||||
|
||||
// StrToUUID converts string to uuid.
|
||||
// s fomat: "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
|
||||
func StrToUUID(s string) (uuid [16]byte, err error) {
|
||||
b := []byte(strings.Replace(s, "-", "", -1))
|
||||
if len(b) != 32 {
|
||||
return uuid, errors.New("invalid UUID: " + s)
|
||||
}
|
||||
_, err = hex.Decode(uuid[:], b)
|
||||
return
|
||||
}
|
||||
|
||||
// GetKey returns the key of AES-128-CFB encrypter
|
||||
// Key:MD5(UUID + []byte('c48619fe-8f02-49e0-b9e9-edf763e17e21'))
|
||||
func GetKey(uuid [16]byte) []byte {
|
||||
md5hash := md5.New()
|
||||
md5hash.Write(uuid[:])
|
||||
md5hash.Write([]byte("c48619fe-8f02-49e0-b9e9-edf763e17e21"))
|
||||
return md5hash.Sum(nil)
|
||||
}
|
||||
|
||||
// TimestampHash returns the iv of AES-128-CFB encrypter
|
||||
// IV:MD5(X + X + X + X),X = []byte(timestamp.now) (8 bytes, Big Endian)
|
||||
func TimestampHash(t time.Time) []byte {
|
||||
md5hash := md5.New()
|
||||
|
||||
ts := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(ts, uint64(t.UTC().Unix()))
|
||||
md5hash.Write(ts)
|
||||
md5hash.Write(ts)
|
||||
md5hash.Write(ts)
|
||||
md5hash.Write(ts)
|
||||
return md5hash.Sum(nil)
|
||||
}
|
102
proxy/vmess/vmess.go
Normal file
102
proxy/vmess/vmess.go
Normal file
@ -0,0 +1,102 @@
|
||||
package vmess
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/nadoo/glider/common/log"
|
||||
"github.com/nadoo/glider/proxy"
|
||||
)
|
||||
|
||||
// VMess struct.
|
||||
type VMess struct {
|
||||
dialer proxy.Dialer
|
||||
addr string
|
||||
|
||||
uuid string
|
||||
alterID int
|
||||
security string
|
||||
|
||||
client *Client
|
||||
}
|
||||
|
||||
func init() {
|
||||
proxy.RegisterDialer("vmess", NewVMessDialer)
|
||||
}
|
||||
|
||||
// NewVMess returns a vmess proxy.
|
||||
func NewVMess(s string, d proxy.Dialer) (*VMess, error) {
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
log.F("parse url err: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addr := u.Host
|
||||
security := u.User.Username()
|
||||
uuid, ok := u.User.Password()
|
||||
if !ok {
|
||||
// no security type specified, vmess://uuid@server
|
||||
uuid = security
|
||||
security = ""
|
||||
}
|
||||
|
||||
query := u.Query()
|
||||
aid := query.Get("alterID")
|
||||
if aid == "" {
|
||||
aid = "0"
|
||||
}
|
||||
|
||||
alterID, err := strconv.ParseUint(aid, 10, 32)
|
||||
if err != nil {
|
||||
log.F("parse alterID err: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client, err := NewClient(uuid, security, int(alterID))
|
||||
if err != nil {
|
||||
log.F("create vmess client err: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p := &VMess{
|
||||
dialer: d,
|
||||
addr: addr,
|
||||
uuid: uuid,
|
||||
alterID: int(alterID),
|
||||
security: security,
|
||||
client: client,
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// NewVMessDialer returns a vmess proxy dialer.
|
||||
func NewVMessDialer(s string, dialer proxy.Dialer) (proxy.Dialer, error) {
|
||||
return NewVMess(s, dialer)
|
||||
}
|
||||
|
||||
// Addr returns forwarder's address.
|
||||
func (s *VMess) Addr() string {
|
||||
if s.addr == "" {
|
||||
return s.dialer.Addr()
|
||||
}
|
||||
return s.addr
|
||||
}
|
||||
|
||||
// Dial connects to the address addr on the network net via the proxy.
|
||||
func (s *VMess) Dial(network, addr string) (net.Conn, error) {
|
||||
rc, err := s.dialer.Dial("tcp", s.addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.client.NewConn(rc, addr)
|
||||
}
|
||||
|
||||
// DialUDP connects to the given address via the proxy.
|
||||
func (s *VMess) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) {
|
||||
return nil, nil, errors.New("vmess client does not support udp now")
|
||||
}
|
128
proxy/ws/client.go
Normal file
128
proxy/ws/client.go
Normal file
@ -0,0 +1,128 @@
|
||||
package ws
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"net/textproto"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var keyGUID = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
|
||||
|
||||
// Client is ws client struct.
|
||||
type Client struct {
|
||||
host string
|
||||
path string
|
||||
}
|
||||
|
||||
// Conn is a connection to ws server.
|
||||
type Conn struct {
|
||||
net.Conn
|
||||
reader io.Reader
|
||||
writer io.Writer
|
||||
}
|
||||
|
||||
// NewClient creates a new ws client.
|
||||
func NewClient(host, path string) (*Client, error) {
|
||||
if path == "" {
|
||||
path = "/"
|
||||
}
|
||||
c := &Client{host: host, path: path}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// NewConn creates a new ws client connection.
|
||||
func (c *Client) NewConn(rc net.Conn, target string) (*Conn, error) {
|
||||
conn := &Conn{Conn: rc}
|
||||
return conn, conn.Handshake(c.host, c.path)
|
||||
}
|
||||
|
||||
// Handshake handshakes with the server using HTTP to request a protocol upgrade.
|
||||
func (c *Conn) Handshake(host, path string) error {
|
||||
clientKey := generateClientKey()
|
||||
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString("GET " + path + " HTTP/1.1\r\n")
|
||||
buf.WriteString("Host: " + host + "\r\n")
|
||||
buf.WriteString("Upgrade: websocket\r\n")
|
||||
buf.WriteString("Connection: Upgrade\r\n")
|
||||
buf.WriteString("Origin: http://" + host + "\r\n")
|
||||
buf.WriteString("Sec-WebSocket-Key: " + clientKey + "\r\n")
|
||||
buf.WriteString("Sec-WebSocket-Protocol: binary\r\n")
|
||||
buf.WriteString("Sec-WebSocket-Version: 13\r\n")
|
||||
buf.WriteString(("\r\n"))
|
||||
|
||||
if _, err := c.Conn.Write(buf.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tpr := textproto.NewReader(bufio.NewReader(c.Conn))
|
||||
line, err := tpr.ReadLine()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, code, _, ok := parseFirstLine(line)
|
||||
if !ok || code != "101" {
|
||||
return errors.New("[ws] error in ws handshake parseFirstLine")
|
||||
}
|
||||
|
||||
respHeader, err := tpr.ReadMIMEHeader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
serverKey := respHeader.Get("Sec-WebSocket-Accept")
|
||||
if serverKey != computeServerKey(clientKey) {
|
||||
return errors.New("[ws] error in ws handshake, got wrong Sec-Websocket-Key")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conn) Write(b []byte) (n int, err error) {
|
||||
if c.writer == nil {
|
||||
c.writer = FrameWriter(c.Conn)
|
||||
}
|
||||
|
||||
return c.writer.Write(b)
|
||||
}
|
||||
|
||||
func (c *Conn) Read(b []byte) (n int, err error) {
|
||||
if c.reader == nil {
|
||||
c.reader = FrameReader(c.Conn)
|
||||
}
|
||||
|
||||
return c.reader.Read(b)
|
||||
}
|
||||
|
||||
// parseFirstLine parses "GET /foo HTTP/1.1" OR "HTTP/1.1 200 OK" into its three parts.
|
||||
// TODO: move to separate http lib package for reuse(also for http proxy module)
|
||||
func parseFirstLine(line string) (r1, r2, r3 string, ok bool) {
|
||||
s1 := strings.Index(line, " ")
|
||||
s2 := strings.Index(line[s1+1:], " ")
|
||||
if s1 < 0 || s2 < 0 {
|
||||
return
|
||||
}
|
||||
s2 += s1 + 1
|
||||
return line[:s1], line[s1+1 : s2], line[s2+1:], true
|
||||
}
|
||||
|
||||
func generateClientKey() string {
|
||||
p := make([]byte, 16)
|
||||
rand.Read(p)
|
||||
return base64.StdEncoding.EncodeToString(p)
|
||||
}
|
||||
|
||||
func computeServerKey(clientKey string) string {
|
||||
h := sha1.New()
|
||||
h.Write([]byte(clientKey))
|
||||
h.Write(keyGUID)
|
||||
return base64.StdEncoding.EncodeToString(h.Sum(nil))
|
||||
}
|
181
proxy/ws/frame.go
Normal file
181
proxy/ws/frame.go
Normal file
@ -0,0 +1,181 @@
|
||||
// https://tools.ietf.org/html/rfc6455#section-5.2
|
||||
//
|
||||
// Frame Format
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-------+-+-------------+-------------------------------+
|
||||
// |F|R|R|R| opcode|M| Payload len | Extended payload length |
|
||||
// |I|S|S|S| (4) |A| (7) | (16/64) |
|
||||
// |N|V|V|V| |S| | (if payload len==126/127) |
|
||||
// | |1|2|3| |K| | |
|
||||
// +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|
||||
// | Extended payload length continued, if payload len == 127 |
|
||||
// + - - - - - - - - - - - - - - - +-------------------------------+
|
||||
// | |Masking-key, if MASK set to 1 |
|
||||
// +-------------------------------+-------------------------------+
|
||||
// | Masking-key (continued) | Payload Data |
|
||||
// +-------------------------------- - - - - - - - - - - - - - - - +
|
||||
// : Payload Data continued ... :
|
||||
// + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|
||||
// | Payload Data continued ... |
|
||||
// +---------------------------------------------------------------+
|
||||
|
||||
package ws
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultFrameSize = 4096
|
||||
maxFrameHeaderSize = 2 + 8 + 4 // Fixed header + length + mask
|
||||
maskKeyLen = 4
|
||||
|
||||
finalBit byte = 1 << 7
|
||||
maskBit byte = 1 << 7
|
||||
opCodeBinary byte = 2
|
||||
)
|
||||
|
||||
type frameWriter struct {
|
||||
io.Writer
|
||||
buf []byte
|
||||
maskKey []byte
|
||||
}
|
||||
|
||||
// FrameWriter returns a frame writer.
|
||||
func FrameWriter(w io.Writer) io.Writer {
|
||||
n := rand.Uint32()
|
||||
return &frameWriter{
|
||||
Writer: w,
|
||||
buf: make([]byte, maxFrameHeaderSize+defaultFrameSize),
|
||||
maskKey: []byte{byte(n), byte(n >> 8), byte(n >> 16), byte(n >> 24)},
|
||||
}
|
||||
}
|
||||
|
||||
func (w *frameWriter) Write(b []byte) (int, error) {
|
||||
n, err := w.ReadFrom(bytes.NewBuffer(b))
|
||||
return int(n), err
|
||||
}
|
||||
|
||||
func (w *frameWriter) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
for {
|
||||
buf := w.buf
|
||||
payloadBuf := buf[maxFrameHeaderSize:]
|
||||
|
||||
nr, er := r.Read(payloadBuf)
|
||||
if nr > 0 {
|
||||
n += int64(nr)
|
||||
buf[0] = finalBit | opCodeBinary
|
||||
buf[1] = maskBit
|
||||
|
||||
lengthFieldLen := 0
|
||||
switch {
|
||||
case nr <= 125:
|
||||
buf[1] |= byte(nr)
|
||||
case nr < 65536:
|
||||
buf[1] |= 126
|
||||
lengthFieldLen = 2
|
||||
binary.BigEndian.PutUint16(buf[2:2+lengthFieldLen], uint16(nr))
|
||||
default:
|
||||
buf[1] |= 127
|
||||
lengthFieldLen = 8
|
||||
binary.BigEndian.PutUint64(buf[2:2+lengthFieldLen], uint64(nr))
|
||||
}
|
||||
|
||||
// header and length
|
||||
_, ew := w.Writer.Write(buf[:2+lengthFieldLen])
|
||||
if ew != nil {
|
||||
err = ew
|
||||
break
|
||||
}
|
||||
|
||||
// maskkey
|
||||
_, ew = w.Writer.Write(w.maskKey)
|
||||
if ew != nil {
|
||||
err = ew
|
||||
break
|
||||
}
|
||||
|
||||
// payload
|
||||
payloadBuf = payloadBuf[:nr]
|
||||
for i := range payloadBuf {
|
||||
payloadBuf[i] = payloadBuf[i] ^ w.maskKey[i%4]
|
||||
}
|
||||
|
||||
_, ew = w.Writer.Write(payloadBuf)
|
||||
if ew != nil {
|
||||
err = ew
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if er != nil {
|
||||
if er != io.EOF { // ignore EOF as per io.ReaderFrom contract
|
||||
err = er
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
type frameReader struct {
|
||||
io.Reader
|
||||
buf []byte
|
||||
leftBytes int64
|
||||
}
|
||||
|
||||
// FrameReader returns a chunked reader.
|
||||
func FrameReader(r io.Reader) io.Reader {
|
||||
return &frameReader{
|
||||
Reader: r,
|
||||
buf: make([]byte, maxFrameHeaderSize), // NOTE: buf only used to save header bytes now
|
||||
}
|
||||
}
|
||||
|
||||
func (r *frameReader) Read(b []byte) (int, error) {
|
||||
if r.leftBytes == 0 {
|
||||
// get msg header
|
||||
_, err := io.ReadFull(r.Reader, r.buf[:2])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// final := r.buf[0]&finalBit != 0
|
||||
// frameType := int(r.buf[0] & 0xf)
|
||||
// mask := r.buf[1]&maskBit != 0
|
||||
r.leftBytes = int64(r.buf[1] & 0x7f)
|
||||
switch r.leftBytes {
|
||||
case 126:
|
||||
_, err := io.ReadFull(r.Reader, r.buf[:2])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
r.leftBytes = int64(binary.BigEndian.Uint16(r.buf[0:]))
|
||||
case 127:
|
||||
_, err := io.ReadFull(r.Reader, r.buf[:8])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
r.leftBytes = int64(binary.BigEndian.Uint64(r.buf[0:]))
|
||||
}
|
||||
}
|
||||
|
||||
readLen := int64(len(b))
|
||||
if readLen > r.leftBytes {
|
||||
readLen = r.leftBytes
|
||||
}
|
||||
|
||||
m, err := r.Reader.Read(b[:readLen])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
r.leftBytes -= int64(m)
|
||||
return m, err
|
||||
}
|
88
proxy/ws/ws.go
Normal file
88
proxy/ws/ws.go
Normal file
@ -0,0 +1,88 @@
|
||||
// Package ws implements a simple websocket client.
|
||||
package ws
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/nadoo/glider/common/log"
|
||||
"github.com/nadoo/glider/proxy"
|
||||
)
|
||||
|
||||
// WS is the base ws proxy struct.
|
||||
type WS struct {
|
||||
dialer proxy.Dialer
|
||||
addr string
|
||||
|
||||
client *Client
|
||||
}
|
||||
|
||||
func init() {
|
||||
proxy.RegisterDialer("ws", NewWSDialer)
|
||||
}
|
||||
|
||||
// NewWS returns a websocket proxy.
|
||||
func NewWS(s string, d proxy.Dialer) (*WS, error) {
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
log.F("parse url err: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addr := u.Host
|
||||
|
||||
// TODO:
|
||||
if addr == "" {
|
||||
addr = d.Addr()
|
||||
}
|
||||
|
||||
colonPos := strings.LastIndex(addr, ":")
|
||||
if colonPos == -1 {
|
||||
colonPos = len(addr)
|
||||
}
|
||||
serverName := addr[:colonPos]
|
||||
|
||||
client, err := NewClient(serverName, u.Path)
|
||||
if err != nil {
|
||||
log.F("create ws client err: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p := &WS{
|
||||
dialer: d,
|
||||
addr: addr,
|
||||
client: client,
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// NewWSDialer returns a ws proxy dialer.
|
||||
func NewWSDialer(s string, d proxy.Dialer) (proxy.Dialer, error) {
|
||||
return NewWS(s, d)
|
||||
}
|
||||
|
||||
// Addr returns forwarder's address.
|
||||
func (s *WS) Addr() string {
|
||||
if s.addr == "" {
|
||||
return s.dialer.Addr()
|
||||
}
|
||||
return s.addr
|
||||
}
|
||||
|
||||
// Dial connects to the address addr on the network net via the proxy.
|
||||
func (s *WS) Dial(network, addr string) (net.Conn, error) {
|
||||
rc, err := s.dialer.Dial("tcp", s.addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.client.NewConn(rc, addr)
|
||||
}
|
||||
|
||||
// DialUDP connects to the given address via the proxy.
|
||||
func (s *WS) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) {
|
||||
return nil, nil, errors.New("ws client does not support udp now")
|
||||
}
|
75
rule/config.go
Normal file
75
rule/config.go
Normal file
@ -0,0 +1,75 @@
|
||||
package rule
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/nadoo/conflag"
|
||||
|
||||
"github.com/nadoo/glider/strategy"
|
||||
)
|
||||
|
||||
// Config , every rule dialer points to a rule file
|
||||
type Config struct {
|
||||
Name string
|
||||
|
||||
Forward []string
|
||||
StrategyConfig strategy.Config
|
||||
|
||||
DNSServers []string
|
||||
IPSet string
|
||||
|
||||
Domain []string
|
||||
IP []string
|
||||
CIDR []string
|
||||
}
|
||||
|
||||
// NewConfFromFile .
|
||||
func NewConfFromFile(ruleFile string) (*Config, error) {
|
||||
p := &Config{Name: ruleFile}
|
||||
|
||||
f := conflag.NewFromFile("rule", ruleFile)
|
||||
f.StringSliceUniqVar(&p.Forward, "forward", nil, "forward url, format: SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS[,SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS]")
|
||||
f.StringVar(&p.StrategyConfig.Strategy, "strategy", "rr", "forward strategy, default: rr")
|
||||
f.StringVar(&p.StrategyConfig.CheckWebSite, "checkwebsite", "www.apple.com", "proxy check HTTP(NOT HTTPS) website address, format: HOST[:PORT], default port: 80")
|
||||
f.IntVar(&p.StrategyConfig.CheckInterval, "checkinterval", 30, "proxy check interval(seconds)")
|
||||
f.IntVar(&p.StrategyConfig.CheckTimeout, "checktimeout", 10, "proxy check timeout(seconds)")
|
||||
f.StringVar(&p.StrategyConfig.IntFace, "interface", "", "source ip or source interface")
|
||||
|
||||
f.StringSliceUniqVar(&p.DNSServers, "dnsserver", nil, "remote dns server")
|
||||
f.StringVar(&p.IPSet, "ipset", "", "ipset name")
|
||||
|
||||
f.StringSliceUniqVar(&p.Domain, "domain", nil, "domain")
|
||||
f.StringSliceUniqVar(&p.IP, "ip", nil, "ip")
|
||||
f.StringSliceUniqVar(&p.CIDR, "cidr", nil, "cidr")
|
||||
|
||||
err := f.Parse()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return p, err
|
||||
}
|
||||
|
||||
// ListDir returns file list named with suffix in dirPth
|
||||
func ListDir(dirPth string, suffix string) (files []string, err error) {
|
||||
files = make([]string, 0, 10)
|
||||
dir, err := ioutil.ReadDir(dirPth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
PthSep := string(os.PathSeparator)
|
||||
suffix = strings.ToUpper(suffix)
|
||||
for _, fi := range dir {
|
||||
if fi.IsDir() {
|
||||
continue
|
||||
}
|
||||
if strings.HasSuffix(strings.ToUpper(fi.Name()), suffix) {
|
||||
files = append(files, dirPth+PthSep+fi.Name())
|
||||
}
|
||||
}
|
||||
return files, nil
|
||||
}
|
137
rule/rule.go
Normal file
137
rule/rule.go
Normal file
@ -0,0 +1,137 @@
|
||||
package rule
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/nadoo/glider/common/log"
|
||||
"github.com/nadoo/glider/proxy"
|
||||
"github.com/nadoo/glider/strategy"
|
||||
)
|
||||
|
||||
// Proxy struct
|
||||
type Proxy struct {
|
||||
proxy *strategy.Proxy
|
||||
proxies []*strategy.Proxy
|
||||
|
||||
domainMap sync.Map
|
||||
ipMap sync.Map
|
||||
cidrMap sync.Map
|
||||
}
|
||||
|
||||
// NewProxy returns a new rule proxy
|
||||
func NewProxy(rules []*Config, proxy *strategy.Proxy) *Proxy {
|
||||
rd := &Proxy{proxy: proxy}
|
||||
|
||||
for _, r := range rules {
|
||||
sd := strategy.NewProxy(r.Forward, &r.StrategyConfig)
|
||||
rd.proxies = append(rd.proxies, sd)
|
||||
|
||||
for _, domain := range r.Domain {
|
||||
rd.domainMap.Store(strings.ToLower(domain), sd)
|
||||
}
|
||||
|
||||
for _, ip := range r.IP {
|
||||
rd.ipMap.Store(ip, sd)
|
||||
}
|
||||
|
||||
for _, s := range r.CIDR {
|
||||
if _, cidr, err := net.ParseCIDR(s); err == nil {
|
||||
rd.cidrMap.Store(cidr, sd)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rd
|
||||
}
|
||||
|
||||
// Dial dials to targer addr and return a conn
|
||||
func (p *Proxy) Dial(network, addr string) (net.Conn, string, error) {
|
||||
return p.nextProxy(addr).Dial(network, addr)
|
||||
}
|
||||
|
||||
// DialUDP connects to the given address via the proxy
|
||||
func (p *Proxy) DialUDP(network, addr string) (pc net.PacketConn, writeTo net.Addr, err error) {
|
||||
return p.nextProxy(addr).DialUDP(network, addr)
|
||||
}
|
||||
|
||||
// nextProxy return next proxy according to rule
|
||||
func (p *Proxy) nextProxy(dstAddr string) *strategy.Proxy {
|
||||
host, _, err := net.SplitHostPort(dstAddr)
|
||||
if err != nil {
|
||||
// TODO: check here
|
||||
// logf("[rule] SplitHostPort ERROR: %s", err)
|
||||
return p.proxy
|
||||
}
|
||||
|
||||
// find ip
|
||||
if ip := net.ParseIP(host); ip != nil {
|
||||
// check ip
|
||||
if proxy, ok := p.ipMap.Load(ip.String()); ok {
|
||||
return proxy.(*strategy.Proxy)
|
||||
}
|
||||
|
||||
var ret *strategy.Proxy
|
||||
// check cidr
|
||||
p.cidrMap.Range(func(key, value interface{}) bool {
|
||||
cidr := key.(*net.IPNet)
|
||||
if cidr.Contains(ip) {
|
||||
ret = value.(*strategy.Proxy)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
if ret != nil {
|
||||
return ret
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
domainParts := strings.Split(host, ".")
|
||||
length := len(domainParts)
|
||||
for i := length - 1; i >= 0; i-- {
|
||||
domain := strings.Join(domainParts[i:length], ".")
|
||||
|
||||
// find in domainMap
|
||||
if proxy, ok := p.domainMap.Load(domain); ok {
|
||||
return proxy.(*strategy.Proxy)
|
||||
}
|
||||
}
|
||||
|
||||
return p.proxy
|
||||
}
|
||||
|
||||
// NextDialer return next dialer according to rule
|
||||
func (p *Proxy) NextDialer(dstAddr string) proxy.Dialer {
|
||||
return p.nextProxy(dstAddr).NextDialer(dstAddr)
|
||||
}
|
||||
|
||||
// AddDomainIP used to update ipMap rules according to domainMap rule
|
||||
func (p *Proxy) AddDomainIP(domain, ip string) error {
|
||||
if ip != "" {
|
||||
domainParts := strings.Split(domain, ".")
|
||||
length := len(domainParts)
|
||||
for i := length - 1; i >= 0; i-- {
|
||||
pDomain := strings.ToLower(strings.Join(domainParts[i:length], "."))
|
||||
|
||||
// find in domainMap
|
||||
if dialer, ok := p.domainMap.Load(pDomain); ok {
|
||||
p.ipMap.Store(ip, dialer)
|
||||
log.F("[rule] add ip=%s, based on rule: domain=%s & domain/ip: %s/%s\n", ip, pDomain, domain, ip)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check .
|
||||
func (p *Proxy) Check() {
|
||||
p.proxy.Check()
|
||||
|
||||
for _, d := range p.proxies {
|
||||
d.Check()
|
||||
}
|
||||
}
|
184
strategy/forward.go
Normal file
184
strategy/forward.go
Normal file
@ -0,0 +1,184 @@
|
||||
package strategy
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/nadoo/glider/common/log"
|
||||
"github.com/nadoo/glider/proxy"
|
||||
)
|
||||
|
||||
// StatusHandler function will be called when the forwarder's status changed
|
||||
type StatusHandler func(*Forwarder)
|
||||
|
||||
// Forwarder is a forwarder
|
||||
type Forwarder struct {
|
||||
proxy.Dialer
|
||||
addr string
|
||||
priority uint32
|
||||
maxFailures uint32 // maxfailures to set to Disabled
|
||||
disabled uint32
|
||||
failures uint32
|
||||
latency int64
|
||||
intface string // local interface or ip address
|
||||
handlers []StatusHandler
|
||||
}
|
||||
|
||||
// ForwarderFromURL parses `forward=` command value and returns a new forwarder
|
||||
func ForwarderFromURL(s, intface string) (f *Forwarder, err error) {
|
||||
f = &Forwarder{}
|
||||
|
||||
ss := strings.Split(s, "#")
|
||||
if len(ss) > 1 {
|
||||
err = f.parseOption(ss[1])
|
||||
}
|
||||
|
||||
iface := intface
|
||||
if f.intface != "" && f.intface != intface {
|
||||
iface = f.intface
|
||||
}
|
||||
|
||||
var d proxy.Dialer
|
||||
d, err = proxy.NewDirect(iface)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, url := range strings.Split(ss[0], ",") {
|
||||
d, err = proxy.DialerFromURL(url, d)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
f.Dialer = d
|
||||
f.addr = d.Addr()
|
||||
|
||||
// set forwarder to disabled by default
|
||||
f.Disable()
|
||||
|
||||
return f, err
|
||||
}
|
||||
|
||||
// DirectForwarder returns a direct forwarder
|
||||
func DirectForwarder(intface string) *Forwarder {
|
||||
d, err := proxy.NewDirect(intface)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &Forwarder{Dialer: d, addr: d.Addr()}
|
||||
}
|
||||
|
||||
func (f *Forwarder) parseOption(option string) error {
|
||||
query, err := url.ParseQuery(option)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var priority uint64
|
||||
p := query.Get("priority")
|
||||
if p != "" {
|
||||
priority, err = strconv.ParseUint(p, 10, 32)
|
||||
}
|
||||
f.SetPriority(uint32(priority))
|
||||
|
||||
f.intface = query.Get("interface")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Addr .
|
||||
func (f *Forwarder) Addr() string {
|
||||
return f.addr
|
||||
}
|
||||
|
||||
// Dial .
|
||||
func (f *Forwarder) Dial(network, addr string) (c net.Conn, err error) {
|
||||
c, err = f.Dialer.Dial(network, addr)
|
||||
if err != nil {
|
||||
f.IncFailures()
|
||||
if f.Failures() >= f.MaxFailures() && f.Enabled() {
|
||||
f.Disable()
|
||||
log.F("[forwarder] %s reaches maxfailures.", f.addr)
|
||||
}
|
||||
}
|
||||
|
||||
return c, err
|
||||
}
|
||||
|
||||
// Failures returns the failuer count of forwarder
|
||||
func (f *Forwarder) Failures() uint32 {
|
||||
return atomic.LoadUint32(&f.failures)
|
||||
}
|
||||
|
||||
// IncFailures increase the failuer count by 1
|
||||
func (f *Forwarder) IncFailures() {
|
||||
atomic.AddUint32(&f.failures, 1)
|
||||
}
|
||||
|
||||
// AddHandler adds a custom handler to handle the status change event
|
||||
func (f *Forwarder) AddHandler(h StatusHandler) {
|
||||
f.handlers = append(f.handlers, h)
|
||||
}
|
||||
|
||||
// Enable the forwarder
|
||||
func (f *Forwarder) Enable() {
|
||||
if atomic.CompareAndSwapUint32(&f.disabled, 1, 0) {
|
||||
for _, h := range f.handlers {
|
||||
h(f)
|
||||
}
|
||||
}
|
||||
atomic.StoreUint32(&f.failures, 0)
|
||||
}
|
||||
|
||||
// Disable the forwarder
|
||||
func (f *Forwarder) Disable() {
|
||||
if atomic.CompareAndSwapUint32(&f.disabled, 0, 1) {
|
||||
for _, h := range f.handlers {
|
||||
h(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enabled returns the status of forwarder
|
||||
func (f *Forwarder) Enabled() bool {
|
||||
return !isTrue(atomic.LoadUint32(&f.disabled))
|
||||
}
|
||||
|
||||
func isTrue(n uint32) bool {
|
||||
return n&1 == 1
|
||||
}
|
||||
|
||||
// Priority returns the priority of forwarder
|
||||
func (f *Forwarder) Priority() uint32 {
|
||||
return atomic.LoadUint32(&f.priority)
|
||||
}
|
||||
|
||||
// SetPriority sets the priority of forwarder
|
||||
func (f *Forwarder) SetPriority(l uint32) {
|
||||
atomic.StoreUint32(&f.priority, l)
|
||||
}
|
||||
|
||||
// MaxFailures returns the maxFailures of forwarder
|
||||
func (f *Forwarder) MaxFailures() uint32 {
|
||||
return atomic.LoadUint32(&f.maxFailures)
|
||||
}
|
||||
|
||||
// SetMaxFailures sets the maxFailures of forwarder
|
||||
func (f *Forwarder) SetMaxFailures(l uint32) {
|
||||
atomic.StoreUint32(&f.maxFailures, l)
|
||||
}
|
||||
|
||||
// Latency returns the latency of forwarder
|
||||
func (f *Forwarder) Latency() int64 {
|
||||
return atomic.LoadInt64(&f.latency)
|
||||
}
|
||||
|
||||
// SetLatency sets the latency of forwarder
|
||||
func (f *Forwarder) SetLatency(l int64) {
|
||||
atomic.StoreInt64(&f.latency, l)
|
||||
}
|
274
strategy/strategy.go
Normal file
274
strategy/strategy.go
Normal file
@ -0,0 +1,274 @@
|
||||
package strategy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"hash/fnv"
|
||||
"io"
|
||||
"net"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/nadoo/glider/common/log"
|
||||
"github.com/nadoo/glider/proxy"
|
||||
)
|
||||
|
||||
// Config is strategy config struct.
|
||||
type Config struct {
|
||||
Strategy string
|
||||
CheckWebSite string
|
||||
CheckInterval int
|
||||
CheckTimeout int
|
||||
MaxFailures int
|
||||
IntFace string
|
||||
}
|
||||
|
||||
// forwarder slice orderd by priority
|
||||
type priSlice []*Forwarder
|
||||
|
||||
func (p priSlice) Len() int { return len(p) }
|
||||
func (p priSlice) Less(i, j int) bool { return p[i].Priority() > p[j].Priority() }
|
||||
func (p priSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||
|
||||
// Proxy is base proxy struct.
|
||||
type Proxy struct {
|
||||
config *Config
|
||||
fwdrs priSlice
|
||||
available []*Forwarder
|
||||
mu sync.RWMutex
|
||||
index uint32
|
||||
priority uint32
|
||||
|
||||
nextForwarder func(addr string) *Forwarder
|
||||
}
|
||||
|
||||
// NewProxy returns a new strategy proxy.
|
||||
func NewProxy(s []string, c *Config) *Proxy {
|
||||
var fwdrs []*Forwarder
|
||||
for _, chain := range s {
|
||||
fwdr, err := ForwarderFromURL(chain, c.IntFace)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fwdr.SetMaxFailures(uint32(c.MaxFailures))
|
||||
fwdrs = append(fwdrs, fwdr)
|
||||
}
|
||||
|
||||
if len(fwdrs) == 0 {
|
||||
// direct forwarder
|
||||
fwdrs = append(fwdrs, DirectForwarder(c.IntFace))
|
||||
c.Strategy = "rr"
|
||||
}
|
||||
|
||||
return newProxy(fwdrs, c)
|
||||
}
|
||||
|
||||
// newProxy returns a new rrProxy
|
||||
func newProxy(fwdrs []*Forwarder, c *Config) *Proxy {
|
||||
d := &Proxy{fwdrs: fwdrs, config: c}
|
||||
sort.Sort(d.fwdrs)
|
||||
|
||||
d.initAvailable()
|
||||
|
||||
if strings.IndexByte(d.config.CheckWebSite, ':') == -1 {
|
||||
d.config.CheckWebSite += ":80"
|
||||
}
|
||||
|
||||
switch c.Strategy {
|
||||
case "rr":
|
||||
d.nextForwarder = d.scheduleRR
|
||||
log.F("[strategy] forward to remote servers in round robin mode.")
|
||||
case "ha":
|
||||
d.nextForwarder = d.scheduleHA
|
||||
log.F("[strategy] forward to remote servers in high availability mode.")
|
||||
case "lha":
|
||||
d.nextForwarder = d.scheduleLHA
|
||||
log.F("[strategy] forward to remote servers in latency based high availability mode.")
|
||||
case "dh":
|
||||
d.nextForwarder = d.scheduleDH
|
||||
log.F("[strategy] forward to remote servers in destination hashing mode.")
|
||||
default:
|
||||
d.nextForwarder = d.scheduleRR
|
||||
log.F("[strategy] not supported forward mode '%s', use round robin mode.", c.Strategy)
|
||||
}
|
||||
|
||||
for _, f := range fwdrs {
|
||||
f.AddHandler(d.onStatusChanged)
|
||||
}
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
// Dial connects to the address addr on the network net.
|
||||
func (p *Proxy) Dial(network, addr string) (net.Conn, string, error) {
|
||||
nd := p.NextDialer(addr)
|
||||
c, err := nd.Dial(network, addr)
|
||||
return c, nd.Addr(), err
|
||||
}
|
||||
|
||||
// DialUDP connects to the given address.
|
||||
func (p *Proxy) DialUDP(network, addr string) (pc net.PacketConn, writeTo net.Addr, err error) {
|
||||
return p.NextDialer(addr).DialUDP(network, addr)
|
||||
}
|
||||
|
||||
// NextDialer returns the next dialer.
|
||||
func (p *Proxy) NextDialer(dstAddr string) proxy.Dialer {
|
||||
p.mu.RLock()
|
||||
defer p.mu.RUnlock()
|
||||
|
||||
return p.nextForwarder(dstAddr)
|
||||
}
|
||||
|
||||
// Priority returns the active priority of dialer.
|
||||
func (p *Proxy) Priority() uint32 { return atomic.LoadUint32(&p.priority) }
|
||||
|
||||
// SetPriority sets the active priority of daler.
|
||||
func (p *Proxy) SetPriority(pri uint32) { atomic.StoreUint32(&p.priority, pri) }
|
||||
|
||||
// initAvailable traverse d.fwdrs and init the available forwarder slice.
|
||||
func (p *Proxy) initAvailable() {
|
||||
for _, f := range p.fwdrs {
|
||||
if f.Enabled() {
|
||||
p.SetPriority(f.Priority())
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
p.available = nil
|
||||
for _, f := range p.fwdrs {
|
||||
if f.Enabled() && f.Priority() >= p.Priority() {
|
||||
p.available = append(p.available, f)
|
||||
}
|
||||
}
|
||||
|
||||
if len(p.available) == 0 {
|
||||
// no available forwarders, set priority to 0 to check all forwarders in check func
|
||||
p.SetPriority(0)
|
||||
log.F("[strategy] no available forwarders, just use: %s, please check your settings or network", p.fwdrs[0].Addr())
|
||||
p.available = append(p.available, p.fwdrs[0])
|
||||
}
|
||||
}
|
||||
|
||||
// onStatusChanged will be called when fwdr's status changed.
|
||||
func (p *Proxy) onStatusChanged(fwdr *Forwarder) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
if fwdr.Enabled() {
|
||||
log.F("[strategy] %s changed status from Disabled to Enabled ", fwdr.Addr())
|
||||
if fwdr.Priority() == p.Priority() {
|
||||
p.available = append(p.available, fwdr)
|
||||
} else if fwdr.Priority() > p.Priority() {
|
||||
p.initAvailable()
|
||||
}
|
||||
} else {
|
||||
log.F("[strategy] %s changed status from Enabled to Disabled", fwdr.Addr())
|
||||
for i, f := range p.available {
|
||||
if f == fwdr {
|
||||
p.available[i], p.available = p.available[len(p.available)-1], p.available[:len(p.available)-1]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(p.available) == 0 {
|
||||
p.initAvailable()
|
||||
}
|
||||
}
|
||||
|
||||
// Check implements the Checker interface.
|
||||
func (p *Proxy) Check() {
|
||||
// no need to check when there's only 1 forwarder
|
||||
if len(p.fwdrs) > 1 {
|
||||
for i := 0; i < len(p.fwdrs); i++ {
|
||||
go p.check(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Proxy) check(i int) {
|
||||
f := p.fwdrs[i]
|
||||
retry := 1
|
||||
buf := make([]byte, 4)
|
||||
|
||||
for {
|
||||
time.Sleep(time.Duration(p.config.CheckInterval) * time.Second * time.Duration(retry>>1))
|
||||
|
||||
// check all forwarders at least one time
|
||||
if retry > 1 && f.Priority() < p.Priority() {
|
||||
continue
|
||||
}
|
||||
|
||||
retry <<= 1
|
||||
if retry > 16 {
|
||||
retry = 16
|
||||
}
|
||||
|
||||
startTime := time.Now()
|
||||
rc, err := f.Dial("tcp", p.config.CheckWebSite)
|
||||
if err != nil {
|
||||
f.Disable()
|
||||
log.F("[check] %s(%d) -> %s, DISABLED. error in dial: %s", f.Addr(), f.Priority(), p.config.CheckWebSite, err)
|
||||
continue
|
||||
}
|
||||
|
||||
rc.Write([]byte("GET / HTTP/1.0\r\n\r\n"))
|
||||
|
||||
_, err = io.ReadFull(rc, buf)
|
||||
if err != nil {
|
||||
f.Disable()
|
||||
log.F("[check] %s(%d) -> %s, DISABLED. error in read: %s", f.Addr(), f.Priority(), p.config.CheckWebSite, err)
|
||||
} else if bytes.Equal([]byte("HTTP"), buf) {
|
||||
|
||||
readTime := time.Since(startTime)
|
||||
f.SetLatency(int64(readTime))
|
||||
|
||||
if readTime > time.Duration(p.config.CheckTimeout)*time.Second {
|
||||
f.Disable()
|
||||
log.F("[check] %s(%d) -> %s, DISABLED. check timeout: %s", f.Addr(), f.Priority(), p.config.CheckWebSite, readTime)
|
||||
} else {
|
||||
retry = 2
|
||||
f.Enable()
|
||||
log.F("[check] %s(%d) -> %s, ENABLED. connect time: %s", f.Addr(), f.Priority(), p.config.CheckWebSite, readTime)
|
||||
}
|
||||
|
||||
} else {
|
||||
f.Disable()
|
||||
log.F("[check] %s(%d) -> %s, DISABLED. server response: %s", f.Addr(), f.Priority(), p.config.CheckWebSite, buf)
|
||||
}
|
||||
|
||||
rc.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// Round Robin
|
||||
func (p *Proxy) scheduleRR(dstAddr string) *Forwarder {
|
||||
return p.available[atomic.AddUint32(&p.index, 1)%uint32(len(p.available))]
|
||||
}
|
||||
|
||||
// High Availability
|
||||
func (p *Proxy) scheduleHA(dstAddr string) *Forwarder {
|
||||
return p.available[0]
|
||||
}
|
||||
|
||||
// Latency based High Availability
|
||||
func (p *Proxy) scheduleLHA(dstAddr string) *Forwarder {
|
||||
fwdr := p.available[0]
|
||||
lowest := fwdr.Latency()
|
||||
for _, f := range p.available {
|
||||
if f.Latency() < lowest {
|
||||
lowest = f.Latency()
|
||||
fwdr = f
|
||||
}
|
||||
}
|
||||
return fwdr
|
||||
}
|
||||
|
||||
// Destination Hashing
|
||||
func (p *Proxy) scheduleDH(dstAddr string) *Forwarder {
|
||||
fnv1a := fnv.New32a()
|
||||
fnv1a.Write([]byte(dstAddr))
|
||||
return p.available[fnv1a.Sum32()%uint32(len(p.available))]
|
||||
}
|
34
systemd/README.md
Normal file
34
systemd/README.md
Normal file
@ -0,0 +1,34 @@
|
||||
## Service
|
||||
|
||||
### Install
|
||||
|
||||
#### 1. copy binary file
|
||||
|
||||
```bash
|
||||
cp glider /usr/bin/
|
||||
```
|
||||
|
||||
#### 2. add service file
|
||||
|
||||
```bash
|
||||
# copy service file to systemd
|
||||
cp systemd/glider@.service /etc/systemd/system/
|
||||
```
|
||||
|
||||
#### 3. add config file: ***server***.conf
|
||||
|
||||
```bash
|
||||
# copy config file to /etc/glider/
|
||||
mkdir /etc/glider/
|
||||
cp ./config/glider.conf.example /etc/glider/server.conf
|
||||
```
|
||||
|
||||
#### 4. enable and start service: glider@***server***
|
||||
|
||||
```bash
|
||||
# enable and start service
|
||||
systemctl enable glider@server
|
||||
systemctl start glider@server
|
||||
```
|
||||
|
||||
See [glider@.service](glider%40.service)
|
22
systemd/glider@.service
Normal file
22
systemd/glider@.service
Normal file
@ -0,0 +1,22 @@
|
||||
[Unit]
|
||||
Description=Glider Service (%i)
|
||||
After=network.target iptables.service ip6tables.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=nobody
|
||||
Restart=always
|
||||
LimitNOFILE=102400
|
||||
|
||||
# NOTE: change to your glider path
|
||||
ExecStart=/usr/bin/glider -config /etc/glider/%i.conf
|
||||
|
||||
# work with systemd v229 or later, so glider can listen on port below 1024 with none-root user
|
||||
# CAP_NET_ADMIN: ipset
|
||||
# CAP_NET_BIND_SERVICE: bind ports under 1024
|
||||
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
|
||||
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
|
||||
NoNewPrivileges=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
Loading…
Reference in New Issue
Block a user