This commit is contained in:
Aero 2019-12-09 10:40:56 +08:00
commit be63cb624c
88 changed files with 9774 additions and 0 deletions

39
.github/workflows/build.yml vendored Normal file
View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1,385 @@
# [glider](https://github.com/nadoo/glider)
[![Go Report Card](https://goreportcard.com/badge/github.com/nadoo/glider?style=flat-square)](https://goreportcard.com/report/github.com/nadoo/glider)
[![GitHub release](https://img.shields.io/github/v/release/nadoo/glider.svg?include_prereleases&style=flat-square)](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
View 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
View 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
View 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
View 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
View 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)

View 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

View File

@ -0,0 +1,5 @@
# Verbose mode, print logs
verbose=True
listen=:8443

View File

@ -0,0 +1,7 @@
# Verbose mode, print logs
verbose=True
listen=:8443
forward=socks5://192.168.1.10:1080

View 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

View 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

View File

@ -0,0 +1,9 @@
# Verbose mode, print logs
verbose=True
listen=:8443
# NOTE HERE:
# Specify a rule file
rulefile=office.rule

View 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

View 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

View 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

View 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

View 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

View File

@ -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

View 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

View 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

View 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.

View File

@ -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

View File

@ -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

View 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

View File

@ -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
View 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
View 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

View 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

View 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

View 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

View File

@ -0,0 +1,7 @@
forward=reject://
ipset=glider
domain=pornhub.com
domain=amazon.com

18
dev.go Normal file
View 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
View File

@ -0,0 +1,7 @@
//+build dev
package main
import (
_ "github.com/nadoo/glider/proxy/tproxy"
)

66
dns/cache.go Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
}

View 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
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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")
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
// KeyMD5(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
// IVMD5(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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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