├── .dockerignore
├── .github
└── workflows
│ ├── docker.yml
│ └── stale.yml
├── .gitignore
├── Dockerfile
├── LICENSE.md
├── Makefile
├── README.md
├── cmd
├── snell-client
│ └── main.go
└── snell-server
│ └── main.go
├── components
├── aead
│ ├── cipher.go
│ └── stream.go
├── simple-obfs
│ ├── http
│ │ ├── client.go
│ │ └── server.go
│ ├── obfs.go
│ └── tls
│ │ ├── client.go
│ │ ├── common.go
│ │ └── server.go
├── snell
│ ├── client.go
│ ├── common.go
│ ├── fastopen.go
│ ├── fastopen_linux.go
│ ├── pool.go
│ └── server.go
├── socks5
│ ├── protocol.go
│ └── server.go
└── utils
│ ├── pool
│ ├── alloc.go
│ └── pool.go
│ └── relay.go
├── constants
└── version.go
├── go.mod
└── go.sum
/.dockerignore:
--------------------------------------------------------------------------------
1 | build/
2 | .git/
3 |
--------------------------------------------------------------------------------
/.github/workflows/docker.yml:
--------------------------------------------------------------------------------
1 | name: Publish Docker Image
2 | on:
3 | workflow_dispatch:
4 | push:
5 | branches:
6 | - master
7 |
8 | concurrency:
9 | group: autobuild-${{ github.ref }}
10 | cancel-in-progress: true
11 |
12 | jobs:
13 | build:
14 | name: Build
15 | runs-on: ubuntu-latest
16 | steps:
17 | - name: Check out code into the Go module directory
18 | uses: actions/checkout@v2
19 | with:
20 | fetch-depth: 0
21 |
22 | - name: Prepare version
23 | run: |
24 | echo "OPEN_SNELL_VERSION=$(make version)" >> "${GITHUB_ENV}"
25 |
26 | - name: Set up QEMU
27 | uses: docker/setup-qemu-action@v1
28 | with:
29 | platforms: all
30 |
31 | - name: Set up docker buildx
32 | id: buildx
33 | uses: docker/setup-buildx-action@v1
34 | with:
35 | version: latest
36 |
37 | - name: Login to DockerHub
38 | uses: docker/login-action@v1
39 | with:
40 | username: ${{ secrets.DOCKER_USERNAME }}
41 | password: ${{ secrets.DOCKER_TOKEN }}
42 |
43 | - name: Login to Github Package
44 | uses: docker/login-action@v1
45 | with:
46 | registry: ghcr.io
47 | username: icpz
48 | password: ${{ secrets.PACKAGE_TOKEN }}
49 |
50 | - name: Build server and push
51 | uses: docker/build-push-action@v2
52 | with:
53 | context: .
54 | platforms: linux/amd64,linux/arm/v7,linux/arm64
55 | push: true
56 | build-args: |
57 | target=server
58 | version=${{ env.OPEN_SNELL_VERSION }}
59 | tags: 'icpz/snell-server:latest,ghcr.io/icpz/snell-server:latest'
60 |
61 | - name: Build client and push
62 | uses: docker/build-push-action@v2
63 | with:
64 | context: .
65 | platforms: linux/amd64,linux/arm/v7,linux/arm64
66 | push: true
67 | build-args: |
68 | target=client
69 | version=${{ env.OPEN_SNELL_VERSION }}
70 | tags: 'icpz/snell-client:latest,ghcr.io/icpz/snell-client:latest'
71 |
72 |
--------------------------------------------------------------------------------
/.github/workflows/stale.yml:
--------------------------------------------------------------------------------
1 | name: 'Close stale issues and PRs'
2 | on:
3 | schedule:
4 | - cron: '30 1 * * *'
5 |
6 | jobs:
7 | stale:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/stale@v4
11 | with:
12 | stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 7 days.'
13 | days-before-stale: 30
14 | days-before-close: 7
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | build/
2 |
3 | *.[oa]
4 | *.sw[po]
5 |
6 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 |
2 | FROM golang:alpine AS builder
3 | ARG target=server
4 | ARG version=nightly
5 | ENV target=${target}
6 | ENV version=${version}
7 |
8 | RUN apk add --no-cache make git
9 | WORKDIR /src
10 | COPY . /src
11 | RUN go mod download && make "VERSION=${version}" ${target} && \
12 | ln -s snell-${target} ./build/entrypoint
13 |
14 |
15 | FROM scratch
16 |
17 | COPY --from=builder /src/build /
18 | ENTRYPOINT [ "/entrypoint" ]
19 |
20 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | ### GNU GENERAL PUBLIC LICENSE
2 |
3 | Version 3, 29 June 2007
4 |
5 | Copyright (C) 2007 Free Software Foundation, Inc.
6 |
7 |
8 | Everyone is permitted to copy and distribute verbatim copies of this
9 | license document, but changing it is not allowed.
10 |
11 | ### Preamble
12 |
13 | The GNU General Public License is a free, copyleft license for
14 | software and other kinds of works.
15 |
16 | The licenses for most software and other practical works are designed
17 | to take away your freedom to share and change the works. By contrast,
18 | the GNU General Public License is intended to guarantee your freedom
19 | to share and change all versions of a program--to make sure it remains
20 | free software for all its users. We, the Free Software Foundation, use
21 | the GNU General Public License for most of our software; it applies
22 | also to any other work released this way by its authors. You can apply
23 | it to your programs, too.
24 |
25 | When we speak of free software, we are referring to freedom, not
26 | price. Our General Public Licenses are designed to make sure that you
27 | have the freedom to distribute copies of free software (and charge for
28 | them if you wish), that you receive source code or can get it if you
29 | want it, that you can change the software or use pieces of it in new
30 | free programs, and that you know you can do these things.
31 |
32 | To protect your rights, we need to prevent others from denying you
33 | these rights or asking you to surrender the rights. Therefore, you
34 | have certain responsibilities if you distribute copies of the
35 | software, or if you modify it: responsibilities to respect the freedom
36 | of others.
37 |
38 | For example, if you distribute copies of such a program, whether
39 | gratis or for a fee, you must pass on to the recipients the same
40 | freedoms that you received. You must make sure that they, too, receive
41 | or can get the source code. And you must show them these terms so they
42 | know their rights.
43 |
44 | Developers that use the GNU GPL protect your rights with two steps:
45 | (1) assert copyright on the software, and (2) offer you this License
46 | giving you legal permission to copy, distribute and/or modify it.
47 |
48 | For the developers' and authors' protection, the GPL clearly explains
49 | that there is no warranty for this free software. For both users' and
50 | authors' sake, the GPL requires that modified versions be marked as
51 | changed, so that their problems will not be attributed erroneously to
52 | authors of previous versions.
53 |
54 | Some devices are designed to deny users access to install or run
55 | modified versions of the software inside them, although the
56 | manufacturer can do so. This is fundamentally incompatible with the
57 | aim of protecting users' freedom to change the software. The
58 | systematic pattern of such abuse occurs in the area of products for
59 | individuals to use, which is precisely where it is most unacceptable.
60 | Therefore, we have designed this version of the GPL to prohibit the
61 | practice for those products. If such problems arise substantially in
62 | other domains, we stand ready to extend this provision to those
63 | domains in future versions of the GPL, as needed to protect the
64 | freedom of users.
65 |
66 | Finally, every program is threatened constantly by software patents.
67 | States should not allow patents to restrict development and use of
68 | software on general-purpose computers, but in those that do, we wish
69 | to avoid the special danger that patents applied to a free program
70 | could make it effectively proprietary. To prevent this, the GPL
71 | assures that patents cannot be used to render the program non-free.
72 |
73 | The precise terms and conditions for copying, distribution and
74 | modification follow.
75 |
76 | ### TERMS AND CONDITIONS
77 |
78 | #### 0. Definitions.
79 |
80 | "This License" refers to version 3 of the GNU General Public License.
81 |
82 | "Copyright" also means copyright-like laws that apply to other kinds
83 | of works, such as semiconductor masks.
84 |
85 | "The Program" refers to any copyrightable work licensed under this
86 | License. Each licensee is addressed as "you". "Licensees" and
87 | "recipients" may be individuals or organizations.
88 |
89 | To "modify" a work means to copy from or adapt all or part of the work
90 | in a fashion requiring copyright permission, other than the making of
91 | an exact copy. The resulting work is called a "modified version" of
92 | the earlier work or a work "based on" the earlier work.
93 |
94 | A "covered work" means either the unmodified Program or a work based
95 | on the Program.
96 |
97 | To "propagate" a work means to do anything with it that, without
98 | permission, would make you directly or secondarily liable for
99 | infringement under applicable copyright law, except executing it on a
100 | computer or modifying a private copy. Propagation includes copying,
101 | distribution (with or without modification), making available to the
102 | public, and in some countries other activities as well.
103 |
104 | To "convey" a work means any kind of propagation that enables other
105 | parties to make or receive copies. Mere interaction with a user
106 | through a computer network, with no transfer of a copy, is not
107 | conveying.
108 |
109 | An interactive user interface displays "Appropriate Legal Notices" to
110 | the extent that it includes a convenient and prominently visible
111 | feature that (1) displays an appropriate copyright notice, and (2)
112 | tells the user that there is no warranty for the work (except to the
113 | extent that warranties are provided), that licensees may convey the
114 | work under this License, and how to view a copy of this License. If
115 | the interface presents a list of user commands or options, such as a
116 | menu, a prominent item in the list meets this criterion.
117 |
118 | #### 1. Source Code.
119 |
120 | The "source code" for a work means the preferred form of the work for
121 | making modifications to it. "Object code" means any non-source form of
122 | a work.
123 |
124 | A "Standard Interface" means an interface that either is an official
125 | standard defined by a recognized standards body, or, in the case of
126 | interfaces specified for a particular programming language, one that
127 | is widely used among developers working in that language.
128 |
129 | The "System Libraries" of an executable work include anything, other
130 | than the work as a whole, that (a) is included in the normal form of
131 | packaging a Major Component, but which is not part of that Major
132 | Component, and (b) serves only to enable use of the work with that
133 | Major Component, or to implement a Standard Interface for which an
134 | implementation is available to the public in source code form. A
135 | "Major Component", in this context, means a major essential component
136 | (kernel, window system, and so on) of the specific operating system
137 | (if any) on which the executable work runs, or a compiler used to
138 | produce the work, or an object code interpreter used to run it.
139 |
140 | The "Corresponding Source" for a work in object code form means all
141 | the source code needed to generate, install, and (for an executable
142 | work) run the object code and to modify the work, including scripts to
143 | control those activities. However, it does not include the work's
144 | System Libraries, or general-purpose tools or generally available free
145 | programs which are used unmodified in performing those activities but
146 | which are not part of the work. For example, Corresponding Source
147 | includes interface definition files associated with source files for
148 | the work, and the source code for shared libraries and dynamically
149 | linked subprograms that the work is specifically designed to require,
150 | such as by intimate data communication or control flow between those
151 | subprograms and other parts of the work.
152 |
153 | The Corresponding Source need not include anything that users can
154 | regenerate automatically from other parts of the Corresponding Source.
155 |
156 | The Corresponding Source for a work in source code form is that same
157 | work.
158 |
159 | #### 2. Basic Permissions.
160 |
161 | All rights granted under this License are granted for the term of
162 | copyright on the Program, and are irrevocable provided the stated
163 | conditions are met. This License explicitly affirms your unlimited
164 | permission to run the unmodified Program. The output from running a
165 | covered work is covered by this License only if the output, given its
166 | content, constitutes a covered work. This License acknowledges your
167 | rights of fair use or other equivalent, as provided by copyright law.
168 |
169 | You may make, run and propagate covered works that you do not convey,
170 | without conditions so long as your license otherwise remains in force.
171 | You may convey covered works to others for the sole purpose of having
172 | them make modifications exclusively for you, or provide you with
173 | facilities for running those works, provided that you comply with the
174 | terms of this License in conveying all material for which you do not
175 | control copyright. Those thus making or running the covered works for
176 | you must do so exclusively on your behalf, under your direction and
177 | control, on terms that prohibit them from making any copies of your
178 | copyrighted material outside their relationship with you.
179 |
180 | Conveying under any other circumstances is permitted solely under the
181 | conditions stated below. Sublicensing is not allowed; section 10 makes
182 | it unnecessary.
183 |
184 | #### 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
185 |
186 | No covered work shall be deemed part of an effective technological
187 | measure under any applicable law fulfilling obligations under article
188 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
189 | similar laws prohibiting or restricting circumvention of such
190 | measures.
191 |
192 | When you convey a covered work, you waive any legal power to forbid
193 | circumvention of technological measures to the extent such
194 | circumvention is effected by exercising rights under this License with
195 | respect to the covered work, and you disclaim any intention to limit
196 | operation or modification of the work as a means of enforcing, against
197 | the work's users, your or third parties' legal rights to forbid
198 | circumvention of technological measures.
199 |
200 | #### 4. Conveying Verbatim Copies.
201 |
202 | You may convey verbatim copies of the Program's source code as you
203 | receive it, in any medium, provided that you conspicuously and
204 | appropriately publish on each copy an appropriate copyright notice;
205 | keep intact all notices stating that this License and any
206 | non-permissive terms added in accord with section 7 apply to the code;
207 | keep intact all notices of the absence of any warranty; and give all
208 | recipients a copy of this License along with the Program.
209 |
210 | You may charge any price or no price for each copy that you convey,
211 | and you may offer support or warranty protection for a fee.
212 |
213 | #### 5. Conveying Modified Source Versions.
214 |
215 | You may convey a work based on the Program, or the modifications to
216 | produce it from the Program, in the form of source code under the
217 | terms of section 4, provided that you also meet all of these
218 | conditions:
219 |
220 | - a) The work must carry prominent notices stating that you modified
221 | it, and giving a relevant date.
222 | - b) The work must carry prominent notices stating that it is
223 | released under this License and any conditions added under
224 | section 7. This requirement modifies the requirement in section 4
225 | to "keep intact all notices".
226 | - c) You must license the entire work, as a whole, under this
227 | License to anyone who comes into possession of a copy. This
228 | License will therefore apply, along with any applicable section 7
229 | additional terms, to the whole of the work, and all its parts,
230 | regardless of how they are packaged. This License gives no
231 | permission to license the work in any other way, but it does not
232 | invalidate such permission if you have separately received it.
233 | - d) If the work has interactive user interfaces, each must display
234 | Appropriate Legal Notices; however, if the Program has interactive
235 | interfaces that do not display Appropriate Legal Notices, your
236 | work need not make them do so.
237 |
238 | A compilation of a covered work with other separate and independent
239 | works, which are not by their nature extensions of the covered work,
240 | and which are not combined with it such as to form a larger program,
241 | in or on a volume of a storage or distribution medium, is called an
242 | "aggregate" if the compilation and its resulting copyright are not
243 | used to limit the access or legal rights of the compilation's users
244 | beyond what the individual works permit. Inclusion of a covered work
245 | in an aggregate does not cause this License to apply to the other
246 | parts of the aggregate.
247 |
248 | #### 6. Conveying Non-Source Forms.
249 |
250 | You may convey a covered work in object code form under the terms of
251 | sections 4 and 5, provided that you also convey the machine-readable
252 | Corresponding Source under the terms of this License, in one of these
253 | ways:
254 |
255 | - a) Convey the object code in, or embodied in, a physical product
256 | (including a physical distribution medium), accompanied by the
257 | Corresponding Source fixed on a durable physical medium
258 | customarily used for software interchange.
259 | - b) Convey the object code in, or embodied in, a physical product
260 | (including a physical distribution medium), accompanied by a
261 | written offer, valid for at least three years and valid for as
262 | long as you offer spare parts or customer support for that product
263 | model, to give anyone who possesses the object code either (1) a
264 | copy of the Corresponding Source for all the software in the
265 | product that is covered by this License, on a durable physical
266 | medium customarily used for software interchange, for a price no
267 | more than your reasonable cost of physically performing this
268 | conveying of source, or (2) access to copy the Corresponding
269 | Source from a network server at no charge.
270 | - c) Convey individual copies of the object code with a copy of the
271 | written offer to provide the Corresponding Source. This
272 | alternative is allowed only occasionally and noncommercially, and
273 | only if you received the object code with such an offer, in accord
274 | with subsection 6b.
275 | - d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 | - e) Convey the object code using peer-to-peer transmission,
288 | provided you inform other peers where the object code and
289 | Corresponding Source of the work are being offered to the general
290 | public at no charge under subsection 6d.
291 |
292 | A separable portion of the object code, whose source code is excluded
293 | from the Corresponding Source as a System Library, need not be
294 | included in conveying the object code work.
295 |
296 | A "User Product" is either (1) a "consumer product", which means any
297 | tangible personal property which is normally used for personal,
298 | family, or household purposes, or (2) anything designed or sold for
299 | incorporation into a dwelling. In determining whether a product is a
300 | consumer product, doubtful cases shall be resolved in favor of
301 | coverage. For a particular product received by a particular user,
302 | "normally used" refers to a typical or common use of that class of
303 | product, regardless of the status of the particular user or of the way
304 | in which the particular user actually uses, or expects or is expected
305 | to use, the product. A product is a consumer product regardless of
306 | whether the product has substantial commercial, industrial or
307 | non-consumer uses, unless such uses represent the only significant
308 | mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to
312 | install and execute modified versions of a covered work in that User
313 | Product from a modified version of its Corresponding Source. The
314 | information must suffice to ensure that the continued functioning of
315 | the modified object code is in no case prevented or interfered with
316 | solely because modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or
331 | updates for a work that has been modified or installed by the
332 | recipient, or for the User Product in which it has been modified or
333 | installed. Access to a network may be denied when the modification
334 | itself materially and adversely affects the operation of the network
335 | or violates the rules and protocols for communication across the
336 | network.
337 |
338 | Corresponding Source conveyed, and Installation Information provided,
339 | in accord with this section must be in a format that is publicly
340 | documented (and with an implementation available to the public in
341 | source code form), and must require no special password or key for
342 | unpacking, reading or copying.
343 |
344 | #### 7. Additional Terms.
345 |
346 | "Additional permissions" are terms that supplement the terms of this
347 | License by making exceptions from one or more of its conditions.
348 | Additional permissions that are applicable to the entire Program shall
349 | be treated as though they were included in this License, to the extent
350 | that they are valid under applicable law. If additional permissions
351 | apply only to part of the Program, that part may be used separately
352 | under those permissions, but the entire Program remains governed by
353 | this License without regard to the additional permissions.
354 |
355 | When you convey a copy of a covered work, you may at your option
356 | remove any additional permissions from that copy, or from any part of
357 | it. (Additional permissions may be written to require their own
358 | removal in certain cases when you modify the work.) You may place
359 | additional permissions on material, added by you to a covered work,
360 | for which you have or can give appropriate copyright permission.
361 |
362 | Notwithstanding any other provision of this License, for material you
363 | add to a covered work, you may (if authorized by the copyright holders
364 | of that material) supplement the terms of this License with terms:
365 |
366 | - a) Disclaiming warranty or limiting liability differently from the
367 | terms of sections 15 and 16 of this License; or
368 | - b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 | - c) Prohibiting misrepresentation of the origin of that material,
372 | or requiring that modified versions of such material be marked in
373 | reasonable ways as different from the original version; or
374 | - d) Limiting the use for publicity purposes of names of licensors
375 | or authors of the material; or
376 | - e) Declining to grant rights under trademark law for use of some
377 | trade names, trademarks, or service marks; or
378 | - f) Requiring indemnification of licensors and authors of that
379 | material by anyone who conveys the material (or modified versions
380 | of it) with contractual assumptions of liability to the recipient,
381 | for any liability that these contractual assumptions directly
382 | impose on those licensors and authors.
383 |
384 | All other non-permissive additional terms are considered "further
385 | restrictions" within the meaning of section 10. If the Program as you
386 | received it, or any part of it, contains a notice stating that it is
387 | governed by this License along with a term that is a further
388 | restriction, you may remove that term. If a license document contains
389 | a further restriction but permits relicensing or conveying under this
390 | License, you may add to a covered work material governed by the terms
391 | of that license document, provided that the further restriction does
392 | not survive such relicensing or conveying.
393 |
394 | If you add terms to a covered work in accord with this section, you
395 | must place, in the relevant source files, a statement of the
396 | additional terms that apply to those files, or a notice indicating
397 | where to find the applicable terms.
398 |
399 | Additional terms, permissive or non-permissive, may be stated in the
400 | form of a separately written license, or stated as exceptions; the
401 | above requirements apply either way.
402 |
403 | #### 8. Termination.
404 |
405 | You may not propagate or modify a covered work except as expressly
406 | provided under this License. Any attempt otherwise to propagate or
407 | modify it is void, and will automatically terminate your rights under
408 | this License (including any patent licenses granted under the third
409 | paragraph of section 11).
410 |
411 | However, if you cease all violation of this License, then your license
412 | from a particular copyright holder is reinstated (a) provisionally,
413 | unless and until the copyright holder explicitly and finally
414 | terminates your license, and (b) permanently, if the copyright holder
415 | fails to notify you of the violation by some reasonable means prior to
416 | 60 days after the cessation.
417 |
418 | Moreover, your license from a particular copyright holder is
419 | reinstated permanently if the copyright holder notifies you of the
420 | violation by some reasonable means, this is the first time you have
421 | received notice of violation of this License (for any work) from that
422 | copyright holder, and you cure the violation prior to 30 days after
423 | your receipt of the notice.
424 |
425 | Termination of your rights under this section does not terminate the
426 | licenses of parties who have received copies or rights from you under
427 | this License. If your rights have been terminated and not permanently
428 | reinstated, you do not qualify to receive new licenses for the same
429 | material under section 10.
430 |
431 | #### 9. Acceptance Not Required for Having Copies.
432 |
433 | You are not required to accept this License in order to receive or run
434 | a copy of the Program. Ancillary propagation of a covered work
435 | occurring solely as a consequence of using peer-to-peer transmission
436 | to receive a copy likewise does not require acceptance. However,
437 | nothing other than this License grants you permission to propagate or
438 | modify any covered work. These actions infringe copyright if you do
439 | not accept this License. Therefore, by modifying or propagating a
440 | covered work, you indicate your acceptance of this License to do so.
441 |
442 | #### 10. Automatic Licensing of Downstream Recipients.
443 |
444 | Each time you convey a covered work, the recipient automatically
445 | receives a license from the original licensors, to run, modify and
446 | propagate that work, subject to this License. You are not responsible
447 | for enforcing compliance by third parties with this License.
448 |
449 | An "entity transaction" is a transaction transferring control of an
450 | organization, or substantially all assets of one, or subdividing an
451 | organization, or merging organizations. If propagation of a covered
452 | work results from an entity transaction, each party to that
453 | transaction who receives a copy of the work also receives whatever
454 | licenses to the work the party's predecessor in interest had or could
455 | give under the previous paragraph, plus a right to possession of the
456 | Corresponding Source of the work from the predecessor in interest, if
457 | the predecessor has it or can get it with reasonable efforts.
458 |
459 | You may not impose any further restrictions on the exercise of the
460 | rights granted or affirmed under this License. For example, you may
461 | not impose a license fee, royalty, or other charge for exercise of
462 | rights granted under this License, and you may not initiate litigation
463 | (including a cross-claim or counterclaim in a lawsuit) alleging that
464 | any patent claim is infringed by making, using, selling, offering for
465 | sale, or importing the Program or any portion of it.
466 |
467 | #### 11. Patents.
468 |
469 | A "contributor" is a copyright holder who authorizes use under this
470 | License of the Program or a work on which the Program is based. The
471 | work thus licensed is called the contributor's "contributor version".
472 |
473 | A contributor's "essential patent claims" are all patent claims owned
474 | or controlled by the contributor, whether already acquired or
475 | hereafter acquired, that would be infringed by some manner, permitted
476 | by this License, of making, using, or selling its contributor version,
477 | but do not include claims that would be infringed only as a
478 | consequence of further modification of the contributor version. For
479 | purposes of this definition, "control" includes the right to grant
480 | patent sublicenses in a manner consistent with the requirements of
481 | this License.
482 |
483 | Each contributor grants you a non-exclusive, worldwide, royalty-free
484 | patent license under the contributor's essential patent claims, to
485 | make, use, sell, offer for sale, import and otherwise run, modify and
486 | propagate the contents of its contributor version.
487 |
488 | In the following three paragraphs, a "patent license" is any express
489 | agreement or commitment, however denominated, not to enforce a patent
490 | (such as an express permission to practice a patent or covenant not to
491 | sue for patent infringement). To "grant" such a patent license to a
492 | party means to make such an agreement or commitment not to enforce a
493 | patent against the party.
494 |
495 | If you convey a covered work, knowingly relying on a patent license,
496 | and the Corresponding Source of the work is not available for anyone
497 | to copy, free of charge and under the terms of this License, through a
498 | publicly available network server or other readily accessible means,
499 | then you must either (1) cause the Corresponding Source to be so
500 | available, or (2) arrange to deprive yourself of the benefit of the
501 | patent license for this particular work, or (3) arrange, in a manner
502 | consistent with the requirements of this License, to extend the patent
503 | license to downstream recipients. "Knowingly relying" means you have
504 | actual knowledge that, but for the patent license, your conveying the
505 | covered work in a country, or your recipient's use of the covered work
506 | in a country, would infringe one or more identifiable patents in that
507 | country that you have reason to believe are valid.
508 |
509 | If, pursuant to or in connection with a single transaction or
510 | arrangement, you convey, or propagate by procuring conveyance of, a
511 | covered work, and grant a patent license to some of the parties
512 | receiving the covered work authorizing them to use, propagate, modify
513 | or convey a specific copy of the covered work, then the patent license
514 | you grant is automatically extended to all recipients of the covered
515 | work and works based on it.
516 |
517 | A patent license is "discriminatory" if it does not include within the
518 | scope of its coverage, prohibits the exercise of, or is conditioned on
519 | the non-exercise of one or more of the rights that are specifically
520 | granted under this License. You may not convey a covered work if you
521 | are a party to an arrangement with a third party that is in the
522 | business of distributing software, under which you make payment to the
523 | third party based on the extent of your activity of conveying the
524 | work, and under which the third party grants, to any of the parties
525 | who would receive the covered work from you, a discriminatory patent
526 | license (a) in connection with copies of the covered work conveyed by
527 | you (or copies made from those copies), or (b) primarily for and in
528 | connection with specific products or compilations that contain the
529 | covered work, unless you entered into that arrangement, or that patent
530 | license was granted, prior to 28 March 2007.
531 |
532 | Nothing in this License shall be construed as excluding or limiting
533 | any implied license or other defenses to infringement that may
534 | otherwise be available to you under applicable patent law.
535 |
536 | #### 12. No Surrender of Others' Freedom.
537 |
538 | If conditions are imposed on you (whether by court order, agreement or
539 | otherwise) that contradict the conditions of this License, they do not
540 | excuse you from the conditions of this License. If you cannot convey a
541 | covered work so as to satisfy simultaneously your obligations under
542 | this License and any other pertinent obligations, then as a
543 | consequence you may not convey it at all. For example, if you agree to
544 | terms that obligate you to collect a royalty for further conveying
545 | from those to whom you convey the Program, the only way you could
546 | satisfy both those terms and this License would be to refrain entirely
547 | from conveying the Program.
548 |
549 | #### 13. Use with the GNU Affero General Public License.
550 |
551 | Notwithstanding any other provision of this License, you have
552 | permission to link or combine any covered work with a work licensed
553 | under version 3 of the GNU Affero General Public License into a single
554 | combined work, and to convey the resulting work. The terms of this
555 | License will continue to apply to the part which is the covered work,
556 | but the special requirements of the GNU Affero General Public License,
557 | section 13, concerning interaction through a network will apply to the
558 | combination as such.
559 |
560 | #### 14. Revised Versions of this License.
561 |
562 | The Free Software Foundation may publish revised and/or new versions
563 | of the GNU General Public License from time to time. Such new versions
564 | will be similar in spirit to the present version, but may differ in
565 | detail to address new problems or concerns.
566 |
567 | Each version is given a distinguishing version number. If the Program
568 | specifies that a certain numbered version of the GNU General Public
569 | License "or any later version" applies to it, you have the option of
570 | following the terms and conditions either of that numbered version or
571 | of any later version published by the Free Software Foundation. If the
572 | Program does not specify a version number of the GNU General Public
573 | License, you may choose any version ever published by the Free
574 | Software Foundation.
575 |
576 | If the Program specifies that a proxy can decide which future versions
577 | of the GNU General Public License can be used, that proxy's public
578 | statement of acceptance of a version permanently authorizes you to
579 | choose that version for the Program.
580 |
581 | Later license versions may give you additional or different
582 | permissions. However, no additional obligations are imposed on any
583 | author or copyright holder as a result of your choosing to follow a
584 | later version.
585 |
586 | #### 15. Disclaimer of Warranty.
587 |
588 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
589 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
590 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT
591 | WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT
592 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
593 | A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND
594 | PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE
595 | DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR
596 | CORRECTION.
597 |
598 | #### 16. Limitation of Liability.
599 |
600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR
602 | CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
603 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES
604 | ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT
605 | NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR
606 | LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM
607 | TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER
608 | PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
609 |
610 | #### 17. Interpretation of Sections 15 and 16.
611 |
612 | If the disclaimer of warranty and limitation of liability provided
613 | above cannot be given local legal effect according to their terms,
614 | reviewing courts shall apply local law that most closely approximates
615 | an absolute waiver of all civil liability in connection with the
616 | Program, unless a warranty or assumption of liability accompanies a
617 | copy of the Program in return for a fee.
618 |
619 | END OF TERMS AND CONDITIONS
620 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | BINDIR=build
2 | PKGDIR=$(CURDIR)
3 | VERSION=$(shell git describe --tags --dirty --always || echo "unknown version")
4 | GOOS=$(shell go env GOOS)
5 | GOARCH=$(shell go env GOARCH)
6 | GOBUILD=GOOS=$(GOOS) GOARCH=$(GOARCH) CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/icpz/open-snell/constants.Version=$(VERSION)" -w -s'
7 | TARGETS := server client
8 |
9 | SRCS := $(shell find $(PKGDIR) -name '*.go')
10 |
11 | all: $(TARGETS)
12 |
13 | $(BINDIR)/%: $(SRCS) go.mod go.sum
14 | $(GOBUILD) -o $@ $(PKGDIR)/cmd/$(@:$(BINDIR)/%=%)
15 |
16 | clean/%:
17 | rm -f $(@:clean/%=$(BINDIR)/%)
18 |
19 | .SECONDEXPANSION:
20 | $(TARGETS): $(BINDIR)/snell-$$@
21 |
22 | clean: $$(patsubst %,clean/snell-%,$$(TARGETS))
23 |
24 | version:
25 | @echo $(VERSION)
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # open-snell
2 |
3 | An open source port of [snell](https://github.com/surge-networks/snell)
4 |
5 | # Features
6 |
7 | + `snell-server`: v1, v2, v3
8 |
9 | + `snell-client`: v1, v2
10 |
11 | **snell-client is bug-fix-only, please consider [clash](https://github.com/Dreamacro/clash) for full feature opensource snell client**
12 |
13 | # Build
14 |
15 | ## Requirements
16 |
17 | + git
18 |
19 | + go 1.17+
20 |
21 | ## Build Steps
22 |
23 | Only tested on macOS.
24 |
25 | ```bash
26 |
27 | # clone and enter the repo
28 |
29 | make
30 |
31 | # or `make server/client' to build snell-server/snell-client separately
32 |
33 | ```
34 |
35 | The binaries are produced at `./build/snell-{server,client}`
36 |
37 | # Usage
38 |
39 | An ini file is needed (compatible with the offical port):
40 |
41 | ```ini
42 | # snell.conf
43 |
44 | # section "snell-client" is used by snell-client
45 | [snell-client]
46 | listen = 0.0.0.0:1234
47 | server = 1.2.3.4:5678
48 | psk = psk
49 | obfs = tls
50 | obfs-host = www.bing.com
51 | version = 1 # default 2
52 |
53 | # section "snell-server" is used by snell-client
54 | [snell-server]
55 | listen = 0.0.0.0:5678
56 | psk = psk
57 | obfs = tls
58 | ```
59 |
60 | Start the `snell-*`:
61 |
62 | ```bash
63 | ./snell-{server,client} -c ./snell.conf
64 | ```
65 |
66 | # Docker image
67 |
68 | The auto-built docker image is also available at [ghcr.io/icpz/snell-server:latest](https://github.com/icpz/open-snell/pkgs/container/snell-server) and [ghcr.io/icpz/snell-client:latest](https://github.com/icpz/open-snell/pkgs/container/snell-client).
69 |
70 | # License
71 |
72 | ```
73 | Copyright (C) 2020-, icpz
74 |
75 | This program is free software: you can redistribute it and/or modify
76 | it under the terms of the GNU General Public License as published by
77 | the Free Software Foundation, either version 3 of the License, or
78 | (at your option) any later version.
79 |
80 | This program is distributed in the hope that it will be useful,
81 | but WITHOUT ANY WARRANTY; without even the implied warranty of
82 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
83 | GNU General Public License for more details.
84 |
85 | You should have received a copy of the GNU General Public License
86 | along with this program. If not, see .
87 | ```
88 |
89 | # Thanks
90 |
91 | + [Dreamacro/clash](https://github.com/Dreamacro/clash)
92 | + [surge-networks/snell](https://github.com/surge-networks/snell)
93 |
94 |
--------------------------------------------------------------------------------
/cmd/snell-client/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of open-snell.
3 | * open-snell is free software: you can redistribute it and/or modify
4 | * it under the terms of the GNU General Public License as published by
5 | * the Free Software Foundation, either version 3 of the License, or
6 | * (at your option) any later version.
7 | * open-snell is distributed in the hope that it will be useful,
8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 | * GNU General Public License for more details.
11 | * You should have received a copy of the GNU General Public License
12 | * along with open-snell. If not, see .
13 | */
14 |
15 | package main
16 |
17 | import (
18 | "flag"
19 | "os"
20 | "os/signal"
21 | "syscall"
22 |
23 | log "github.com/golang/glog"
24 | "gopkg.in/ini.v1"
25 |
26 | "github.com/icpz/open-snell/components/snell"
27 | "github.com/icpz/open-snell/constants"
28 | )
29 |
30 | var (
31 | configFile string
32 | listenAddr string
33 | serverAddr string
34 | obfsType string
35 | obfsHost string
36 | psk string
37 | snellVer string
38 | version bool
39 | )
40 |
41 | func init() {
42 | flag.StringVar(&configFile, "c", "", "configuration file path")
43 | flag.StringVar(&listenAddr, "l", "0.0.0.0:18888", "client listen address")
44 | flag.StringVar(&serverAddr, "s", "", "snell server address")
45 | flag.StringVar(&obfsType, "obfs", "", "obfs type")
46 | flag.StringVar(&obfsHost, "obfs-host", "bing.com", "obfs host")
47 | flag.StringVar(&psk, "k", "", "pre-shared key")
48 | flag.BoolVar(&version, "version", false, "show open-snell version")
49 |
50 | flag.Parse()
51 | flag.Set("logtostderr", "true")
52 |
53 | log.Infof("Open-snell client, version: %s\n", constants.Version)
54 | if version {
55 | os.Exit(0)
56 | }
57 |
58 | if configFile != "" {
59 | log.Infof("Configuration file specified, ignoring other flags\n")
60 | cfg, err := ini.Load(configFile)
61 | if err != nil {
62 | log.Fatalf("Failed to load config file %s, %v\n", configFile, err)
63 | }
64 | sec, err := cfg.GetSection("snell-client")
65 | if err != nil {
66 | log.Fatalf("Section 'snell-client' not found in config file %s\n", configFile)
67 | }
68 |
69 | listenAddr = sec.Key("listen").String()
70 | serverAddr = sec.Key("server").String()
71 | obfsType = sec.Key("obfs").String()
72 | obfsHost = sec.Key("obfs-host").String()
73 | psk = sec.Key("psk").String()
74 | snellVer = sec.Key("version").String()
75 | }
76 |
77 | if serverAddr == "" {
78 | log.Fatalf("Invalid emtpy server address.\n")
79 | }
80 |
81 | if obfsHost == "" {
82 | log.Infof("Note: obfs host empty, using default bing.com\n")
83 | obfsHost = "bing.com"
84 | }
85 |
86 | if obfsType == "none" || obfsType == "off" {
87 | obfsType = ""
88 | }
89 |
90 | if snellVer == "" {
91 | snellVer = "2"
92 | }
93 | }
94 |
95 | func main() {
96 | sn, err := snell.NewSnellClient(listenAddr, serverAddr, obfsType, obfsHost, psk, snellVer == "2")
97 | if err != nil {
98 | log.Fatalf("Failed to initialize snell client %v\n", err)
99 | }
100 |
101 | sigCh := make(chan os.Signal, 1)
102 | signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
103 | <-sigCh
104 |
105 | sn.Close()
106 | }
107 |
--------------------------------------------------------------------------------
/cmd/snell-server/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of open-snell.
3 | * open-snell is free software: you can redistribute it and/or modify
4 | * it under the terms of the GNU General Public License as published by
5 | * the Free Software Foundation, either version 3 of the License, or
6 | * (at your option) any later version.
7 | * open-snell is distributed in the hope that it will be useful,
8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 | * GNU General Public License for more details.
11 | * You should have received a copy of the GNU General Public License
12 | * along with open-snell. If not, see .
13 | */
14 |
15 | package main
16 |
17 | import (
18 | "flag"
19 | "os"
20 | "os/signal"
21 | "syscall"
22 |
23 | log "github.com/golang/glog"
24 | "gopkg.in/ini.v1"
25 |
26 | "github.com/icpz/open-snell/components/snell"
27 | "github.com/icpz/open-snell/constants"
28 | )
29 |
30 | var (
31 | configFile string
32 | listenAddr string
33 | obfsType string
34 | psk string
35 | version bool
36 | )
37 |
38 | func init() {
39 | flag.StringVar(&configFile, "c", "", "configuration file path")
40 | flag.StringVar(&listenAddr, "l", "0.0.0.0:18888", "server listen address")
41 | flag.StringVar(&obfsType, "obfs", "", "obfs type")
42 | flag.StringVar(&psk, "k", "", "pre-shared key")
43 | flag.BoolVar(&version, "version", false, "show open-snell version")
44 |
45 | flag.Parse()
46 | flag.Set("logtostderr", "true")
47 |
48 | log.Infof("Open-snell server, version: %s\n", constants.Version)
49 | if version {
50 | os.Exit(0)
51 | }
52 |
53 | if configFile != "" {
54 | log.Infof("Configuration file specified, ignoring other flags\n")
55 | cfg, err := ini.Load(configFile)
56 | if err != nil {
57 | log.Fatalf("Failed to load config file %s, %v\n", configFile, err)
58 | }
59 | sec, err := cfg.GetSection("snell-server")
60 | if err != nil {
61 | log.Fatalf("Section 'snell-server' not found in config file %s\n", configFile)
62 | }
63 |
64 | listenAddr = sec.Key("listen").String()
65 | obfsType = sec.Key("obfs").String()
66 | psk = sec.Key("psk").String()
67 | }
68 |
69 | if obfsType == "none" || obfsType == "off" {
70 | obfsType = ""
71 | }
72 | }
73 |
74 | func main() {
75 | sn, err := snell.NewSnellServer(listenAddr, psk, obfsType)
76 | if err != nil {
77 | log.Fatalf("Failed to initialize snell server %v\n", err)
78 | }
79 |
80 | sigCh := make(chan os.Signal, 1)
81 | signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
82 | <-sigCh
83 |
84 | sn.Close()
85 | }
86 |
--------------------------------------------------------------------------------
/components/aead/cipher.go:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of open-snell.
3 | * open-snell is free software: you can redistribute it and/or modify
4 | * it under the terms of the GNU General Public License as published by
5 | * the Free Software Foundation, either version 3 of the License, or
6 | * (at your option) any later version.
7 | * open-snell is distributed in the hope that it will be useful,
8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 | * GNU General Public License for more details.
11 | * You should have received a copy of the GNU General Public License
12 | * along with open-snell. If not, see .
13 | */
14 |
15 | package aead
16 |
17 | import (
18 | "crypto/aes"
19 | "crypto/cipher"
20 |
21 | "golang.org/x/crypto/argon2"
22 | "golang.org/x/crypto/chacha20poly1305"
23 | )
24 |
25 | type Cipher interface {
26 | KeySize() int
27 | SaltSize() int
28 | Encrypter(salt []byte) (cipher.AEAD, error)
29 | Decrypter(salt []byte) (cipher.AEAD, error)
30 | }
31 |
32 | type snellCipher struct {
33 | psk []byte
34 | keySize int
35 | makeAEAD func(key []byte) (cipher.AEAD, error)
36 | }
37 |
38 | func (sc *snellCipher) KeySize() int { return sc.keySize }
39 | func (sc *snellCipher) SaltSize() int { return 16 }
40 | func (sc *snellCipher) Encrypter(salt []byte) (cipher.AEAD, error) {
41 | return sc.makeAEAD(snellKDF(sc.psk, salt, sc.KeySize()))
42 | }
43 | func (sc *snellCipher) Decrypter(salt []byte) (cipher.AEAD, error) {
44 | return sc.makeAEAD(snellKDF(sc.psk, salt, sc.KeySize()))
45 | }
46 |
47 | func snellKDF(psk, salt []byte, keySize int) []byte {
48 | return argon2.IDKey(psk, salt, 3, 8, 1, 32)[:keySize]
49 | }
50 |
51 | func aesGCM(key []byte) (cipher.AEAD, error) {
52 | blk, err := aes.NewCipher(key)
53 | if err != nil {
54 | return nil, err
55 | }
56 | return cipher.NewGCM(blk)
57 | }
58 |
59 | func NewAES128GCM(psk []byte) Cipher {
60 | return &snellCipher{
61 | psk: psk,
62 | keySize: 16,
63 | makeAEAD: aesGCM,
64 | }
65 | }
66 |
67 | func NewChacha20Poly1305(psk []byte) Cipher {
68 | return &snellCipher{
69 | psk: psk,
70 | keySize: 32,
71 | makeAEAD: chacha20poly1305.New,
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/components/aead/stream.go:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of open-snell.
3 | * open-snell is free software: you can redistribute it and/or modify
4 | * it under the terms of the GNU General Public License as published by
5 | * the Free Software Foundation, either version 3 of the License, or
6 | * (at your option) any later version.
7 | * open-snell is distributed in the hope that it will be useful,
8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 | * GNU General Public License for more details.
11 | * You should have received a copy of the GNU General Public License
12 | * along with open-snell. If not, see .
13 | */
14 |
15 | package aead
16 |
17 | import (
18 | "bytes"
19 | "crypto/cipher"
20 | "crypto/rand"
21 | "errors"
22 | "io"
23 | "net"
24 | "sync"
25 | )
26 |
27 | const payloadSizeMask = 0x3FFF // 16*1024 - 1
28 |
29 | var ErrZeroChunk = errors.New("Snell ZERO_CHUNK occurred")
30 |
31 | type writer struct {
32 | io.Writer
33 | cipher.AEAD
34 | nonce []byte
35 | buf []byte
36 | mux sync.Mutex
37 | }
38 |
39 | func NewWriter(w io.Writer, aead cipher.AEAD) io.Writer { return newWriter(w, aead) }
40 |
41 | func newWriter(w io.Writer, aead cipher.AEAD) *writer {
42 | return &writer{
43 | Writer: w,
44 | AEAD: aead,
45 | buf: make([]byte, 2+aead.Overhead()+payloadSizeMask+aead.Overhead()),
46 | nonce: make([]byte, aead.NonceSize()),
47 | }
48 | }
49 |
50 | func (w *writer) Write(b []byte) (int, error) {
51 | if len(b) == 0 { // zero chunk
52 | w.mux.Lock()
53 | defer w.mux.Unlock()
54 |
55 | buf := w.buf
56 | buf = buf[:2+w.Overhead()]
57 |
58 | buf[0], buf[1] = 0, 0
59 | w.Seal(buf[:0], w.nonce, buf[:2], nil)
60 | increment(w.nonce)
61 |
62 | _, err := w.Writer.Write(buf)
63 | return 0, err
64 | }
65 |
66 | n, err := w.ReadFrom(bytes.NewBuffer(b))
67 | return int(n), err
68 | }
69 |
70 | func (w *writer) ReadFrom(r io.Reader) (n int64, err error) {
71 | w.mux.Lock()
72 | defer w.mux.Unlock()
73 |
74 | for {
75 | buf := w.buf
76 | payloadBuf := buf[2+w.Overhead() : 2+w.Overhead()+payloadSizeMask]
77 | nr, er := r.Read(payloadBuf)
78 |
79 | if nr > 0 {
80 | n += int64(nr)
81 | buf = buf[:2+w.Overhead()+nr+w.Overhead()]
82 | payloadBuf = payloadBuf[:nr]
83 | buf[0], buf[1] = byte(nr>>8), byte(nr) // big-endian payload size
84 | w.Seal(buf[:0], w.nonce, buf[:2], nil)
85 | increment(w.nonce)
86 |
87 | w.Seal(payloadBuf[:0], w.nonce, payloadBuf, nil)
88 | increment(w.nonce)
89 |
90 | _, ew := w.Writer.Write(buf)
91 | if ew != nil {
92 | err = ew
93 | break
94 | }
95 | }
96 |
97 | if er != nil {
98 | if er != io.EOF { // ignore EOF as per io.ReaderFrom contract
99 | err = er
100 | }
101 | break
102 | }
103 | }
104 |
105 | return n, err
106 | }
107 |
108 | type reader struct {
109 | io.Reader
110 | cipher.AEAD
111 | nonce []byte
112 | buf []byte
113 | leftover []byte
114 | fallback cipher.AEAD
115 | switched bool
116 | mux sync.Mutex
117 | }
118 |
119 | // NewReader wraps an io.Reader with AEAD decryption.
120 | func NewReader(r io.Reader, aead cipher.AEAD) io.Reader { return newReader(r, aead, nil) }
121 |
122 | func NewReaderWithFallback(r io.Reader, aead, fallback cipher.AEAD) io.Reader {
123 | return newReader(r, aead, fallback)
124 | }
125 |
126 | func newReader(r io.Reader, aead cipher.AEAD, fallback cipher.AEAD) *reader {
127 | return &reader{
128 | Reader: r,
129 | AEAD: aead,
130 | buf: make([]byte, payloadSizeMask+aead.Overhead()),
131 | nonce: make([]byte, aead.NonceSize()),
132 | fallback: fallback,
133 | }
134 | }
135 |
136 | // read and decrypt a record into the internal buffer. Return decrypted payload length and any error encountered.
137 | func (r *reader) read() (int, error) {
138 | // decrypt payload size
139 | buf := r.buf[:2+r.Overhead()]
140 | _, err := io.ReadFull(r.Reader, buf)
141 | if err != nil {
142 | return 0, err
143 | }
144 |
145 | if r.fallback != nil {
146 | tbuf := make([]byte, len(buf))
147 | copy(tbuf, buf)
148 | _, err = r.Open(buf[:0], r.nonce, tbuf, nil)
149 | if err != nil {
150 | r.AEAD = r.fallback
151 | r.switched = true
152 | _, err = r.Open(buf[:0], r.nonce, tbuf, nil)
153 | }
154 | r.fallback = nil
155 | } else {
156 | _, err = r.Open(buf[:0], r.nonce, buf, nil)
157 | }
158 | increment(r.nonce)
159 | if err != nil {
160 | return 0, err
161 | }
162 |
163 | size := (int(buf[0])<<8 + int(buf[1])) & payloadSizeMask
164 |
165 | if size == 0 {
166 | return 0, ErrZeroChunk
167 | }
168 |
169 | // decrypt payload
170 | buf = r.buf[:size+r.Overhead()]
171 | _, err = io.ReadFull(r.Reader, buf)
172 | if err != nil {
173 | return 0, err
174 | }
175 |
176 | _, err = r.Open(buf[:0], r.nonce, buf, nil)
177 | increment(r.nonce)
178 | if err != nil {
179 | return 0, err
180 | }
181 |
182 | return size, nil
183 | }
184 |
185 | // Read reads from the embedded io.Reader, decrypts and writes to b.
186 | func (r *reader) Read(b []byte) (int, error) {
187 | r.mux.Lock()
188 | defer r.mux.Unlock()
189 |
190 | // copy decrypted bytes (if any) from previous record first
191 | if len(r.leftover) > 0 {
192 | n := copy(b, r.leftover)
193 | r.leftover = r.leftover[n:]
194 | return n, nil
195 | }
196 |
197 | n, err := r.read()
198 | m := copy(b, r.buf[:n])
199 | if m < n { // insufficient len(b), keep leftover for next read
200 | r.leftover = r.buf[m:n]
201 | }
202 | return m, err
203 | }
204 |
205 | // WriteTo reads from the embedded io.Reader, decrypts and writes to w until
206 | // there's no more data to write or when an error occurs. Return number of
207 | // bytes written to w and any error encountered.
208 | func (r *reader) WriteTo(w io.Writer) (n int64, err error) {
209 | r.mux.Lock()
210 | defer r.mux.Unlock()
211 |
212 | // write decrypted bytes left over from previous record
213 | for len(r.leftover) > 0 {
214 | nw, ew := w.Write(r.leftover)
215 | r.leftover = r.leftover[nw:]
216 | n += int64(nw)
217 | if ew != nil {
218 | return n, ew
219 | }
220 | }
221 |
222 | for {
223 | nr, er := r.read()
224 | if nr > 0 {
225 | nw, ew := w.Write(r.buf[:nr])
226 | n += int64(nw)
227 |
228 | if ew != nil {
229 | err = ew
230 | break
231 | }
232 | }
233 |
234 | if er != nil {
235 | if er != io.EOF { // ignore EOF as per io.Copy contract (using src.WriteTo shortcut)
236 | err = er
237 | }
238 | break
239 | }
240 | }
241 |
242 | return n, err
243 | }
244 |
245 | // increment little-endian encoded unsigned integer b. Wrap around on overflow.
246 | func increment(b []byte) {
247 | for i := range b {
248 | b[i]++
249 | if b[i] != 0 {
250 | return
251 | }
252 | }
253 | }
254 |
255 | type streamConn struct {
256 | net.Conn
257 | Cipher
258 | r *reader
259 | w *writer
260 | fallback Cipher
261 | }
262 |
263 | func (c *streamConn) initReader() error {
264 | salt := make([]byte, c.SaltSize())
265 | if _, err := io.ReadFull(c.Conn, salt); err != nil {
266 | return err
267 | }
268 | aead, err := c.Decrypter(salt)
269 | if err != nil {
270 | return err
271 | }
272 |
273 | var fallback cipher.AEAD = nil
274 | if c.fallback != nil {
275 | fallback, _ = c.fallback.Decrypter(salt)
276 | }
277 |
278 | c.r = newReader(c.Conn, aead, fallback)
279 | return nil
280 | }
281 |
282 | func (c *streamConn) Read(b []byte) (int, error) {
283 | if c.r == nil {
284 | if err := c.initReader(); err != nil {
285 | return 0, err
286 | }
287 | n, err := c.r.Read(b)
288 | if c.r.switched { // cipher switched
289 | c.Cipher = c.fallback
290 | c.fallback = nil
291 | }
292 | return n, err
293 | }
294 | return c.r.Read(b)
295 | }
296 |
297 | func (c *streamConn) WriteTo(w io.Writer) (int64, error) {
298 | if c.r == nil {
299 | if err := c.initReader(); err != nil {
300 | return 0, err
301 | }
302 | n, err := c.r.WriteTo(w)
303 | if c.r.switched { // cipher switched
304 | c.Cipher = c.fallback
305 | c.fallback = nil
306 | }
307 | return n, err
308 | }
309 | return c.r.WriteTo(w)
310 | }
311 |
312 | func (c *streamConn) initWriter() error {
313 | salt := make([]byte, c.SaltSize())
314 | if _, err := io.ReadFull(rand.Reader, salt); err != nil {
315 | return err
316 | }
317 | aead, err := c.Encrypter(salt)
318 | if err != nil {
319 | return err
320 | }
321 | _, err = c.Conn.Write(salt)
322 | if err != nil {
323 | return err
324 | }
325 | c.w = newWriter(c.Conn, aead)
326 | return nil
327 | }
328 |
329 | func (c *streamConn) Write(b []byte) (int, error) {
330 | if c.w == nil {
331 | if err := c.initWriter(); err != nil {
332 | return 0, err
333 | }
334 | }
335 | return c.w.Write(b)
336 | }
337 |
338 | func (c *streamConn) ReadFrom(r io.Reader) (int64, error) {
339 | if c.w == nil {
340 | if err := c.initWriter(); err != nil {
341 | return 0, err
342 | }
343 | }
344 | return c.w.ReadFrom(r)
345 | }
346 |
347 | // NewConn wraps a stream-oriented net.Conn with cipher.
348 | func NewConn(c net.Conn, ciph Cipher) net.Conn { return &streamConn{Conn: c, Cipher: ciph} }
349 |
350 | func NewConnWithFallback(c net.Conn, ciph, fallback Cipher) net.Conn {
351 | return &streamConn{
352 | Conn: c,
353 | Cipher: ciph,
354 | fallback: fallback,
355 | }
356 | }
357 |
--------------------------------------------------------------------------------
/components/simple-obfs/http/client.go:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of open-snell.
3 | * open-snell is free software: you can redistribute it and/or modify
4 | * it under the terms of the GNU General Public License as published by
5 | * the Free Software Foundation, either version 3 of the License, or
6 | * (at your option) any later version.
7 | * open-snell is distributed in the hope that it will be useful,
8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 | * GNU General Public License for more details.
11 | * You should have received a copy of the GNU General Public License
12 | * along with open-snell. If not, see .
13 | */
14 |
15 | package http
16 |
17 | import (
18 | "bufio"
19 | "bytes"
20 | "encoding/base64"
21 | "fmt"
22 | "io/ioutil"
23 | "math/rand"
24 | "net"
25 | "net/http"
26 | )
27 |
28 | type HTTPObfsClient struct {
29 | net.Conn
30 | host string
31 | port string
32 | bio *bufio.Reader
33 | buf []byte
34 | offset int
35 | firstRequest bool
36 | firstResponse bool
37 | }
38 |
39 | func (ho *HTTPObfsClient) Read(b []byte) (int, error) {
40 | if ho.buf != nil {
41 | n := copy(b, ho.buf[ho.offset:])
42 | ho.offset += n
43 | if ho.offset == len(ho.buf) {
44 | ho.offset = 0
45 | ho.buf = nil
46 | }
47 | return n, nil
48 | }
49 |
50 | if ho.firstResponse {
51 | bio := bufio.NewReader(ho.Conn)
52 | resp, err := http.ReadResponse(bio, nil)
53 | if err != nil {
54 | return 0, err
55 | }
56 |
57 | buf, err := ioutil.ReadAll(resp.Body)
58 | if err != nil {
59 | return 0, err
60 | }
61 | n := copy(b, buf)
62 | if n < len(buf) {
63 | ho.buf = buf
64 | ho.offset = n
65 | }
66 | resp.Body.Close()
67 | ho.bio = bio
68 | ho.firstResponse = false
69 | return n, nil
70 | }
71 | return ho.bio.Read(b)
72 | }
73 |
74 | func (ho *HTTPObfsClient) Write(b []byte) (int, error) {
75 | if ho.firstRequest {
76 | randBytes := make([]byte, 16)
77 | rand.Read(randBytes)
78 | req, _ := http.NewRequest("GET", fmt.Sprintf("http://%s/", ho.host), bytes.NewBuffer(b[:]))
79 | req.Header.Set("User-Agent", fmt.Sprintf("curl/7.%d.%d", rand.Int()%54, rand.Int()%2))
80 | req.Header.Set("Upgrade", "websocket")
81 | req.Header.Set("Connection", "Upgrade")
82 | req.Host = fmt.Sprintf("%s:%s", ho.host, ho.port)
83 | req.Header.Set("Sec-WebSocket-Key", base64.URLEncoding.EncodeToString(randBytes))
84 | req.ContentLength = int64(len(b))
85 | err := req.Write(ho.Conn)
86 | ho.firstRequest = false
87 | return len(b), err
88 | }
89 |
90 | return ho.Conn.Write(b)
91 | }
92 |
93 | func NewHTTPObfsClient(conn net.Conn, host string, port string) net.Conn {
94 | return &HTTPObfsClient{
95 | Conn: conn,
96 | firstRequest: true,
97 | firstResponse: true,
98 | host: host,
99 | port: port,
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/components/simple-obfs/http/server.go:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of open-snell.
3 | * open-snell is free software: you can redistribute it and/or modify
4 | * it under the terms of the GNU General Public License as published by
5 | * the Free Software Foundation, either version 3 of the License, or
6 | * (at your option) any later version.
7 | * open-snell is distributed in the hope that it will be useful,
8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 | * GNU General Public License for more details.
11 | * You should have received a copy of the GNU General Public License
12 | * along with open-snell. If not, see .
13 | */
14 |
15 | package http
16 |
17 | import (
18 | "bufio"
19 | "encoding/base64"
20 | "fmt"
21 | "io"
22 | "io/ioutil"
23 | "math/rand"
24 | "net"
25 | "net/http"
26 | "time"
27 | )
28 |
29 | type HTTPObfsServer struct {
30 | net.Conn
31 | buf []byte
32 | bio *bufio.Reader
33 | offset int
34 | firstRequest bool
35 | firstResponse bool
36 | }
37 |
38 | func (hos *HTTPObfsServer) Read(b []byte) (int, error) {
39 | if hos.buf != nil {
40 | n := copy(b, hos.buf[hos.offset:])
41 | hos.offset += n
42 | if hos.offset == len(hos.buf) {
43 | hos.offset = 0
44 | hos.buf = nil
45 | }
46 | return n, nil
47 | }
48 |
49 | if hos.firstRequest {
50 | bio := bufio.NewReader(hos.Conn)
51 | req, err := http.ReadRequest(bio)
52 | if err != nil {
53 | return 0, err
54 | }
55 | if req.Method != "GET" || req.Header.Get("Connection") != "Upgrade" {
56 | return 0, io.EOF
57 | }
58 |
59 | buf, err := ioutil.ReadAll(req.Body)
60 | if err != nil {
61 | return 0, err
62 | }
63 | n := copy(b, buf)
64 | if n < len(buf) {
65 | hos.buf = buf
66 | hos.offset = n
67 | }
68 | req.Body.Close()
69 | hos.bio = bio
70 | hos.firstRequest = false
71 | return n, nil
72 | }
73 |
74 | return hos.bio.Read(b)
75 | }
76 |
77 | const httpResponseTemplate = "HTTP/1.1 101 Switching Protocols\r\n" +
78 | "Server: nginx/1.%d.%d\r\n" +
79 | "Date: %s\r\n" +
80 | "Upgrade: websocket\r\n" +
81 | "Connection: Upgrade\r\n" +
82 | "Sec-WebSocket-Accept: %s\r\n" +
83 | "\r\n"
84 |
85 | var vMajor = rand.Int() % 11
86 | var vMinor = rand.Int() % 12
87 |
88 | func (hos *HTTPObfsServer) Write(b []byte) (int, error) {
89 | if hos.firstResponse {
90 | randBytes := make([]byte, 16)
91 | rand.Read(randBytes)
92 | date := time.Now().Format(time.RFC1123)
93 | resp := fmt.Sprintf(httpResponseTemplate, vMajor, vMinor, date, base64.URLEncoding.EncodeToString(randBytes))
94 | _, err := hos.Conn.Write([]byte(resp))
95 | if err != nil {
96 | return 0, err
97 | }
98 | hos.firstResponse = false
99 | _, err = hos.Conn.Write(b)
100 | return len(b), err
101 | }
102 | return hos.Conn.Write(b)
103 | }
104 |
105 | func NewHTTPObfsServer(conn net.Conn) net.Conn {
106 | return &HTTPObfsServer{
107 | Conn: conn,
108 | buf: nil,
109 | bio: nil,
110 | offset: 0,
111 | firstRequest: true,
112 | firstResponse: true,
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/components/simple-obfs/obfs.go:
--------------------------------------------------------------------------------
1 | package obfs
2 |
3 | import (
4 | "fmt"
5 | "net"
6 |
7 | "github.com/icpz/open-snell/components/simple-obfs/http"
8 | "github.com/icpz/open-snell/components/simple-obfs/tls"
9 | )
10 |
11 | func NewObfsServer(conn net.Conn, obfs string) (c net.Conn, err error) {
12 | switch obfs {
13 | case "tls":
14 | c = tls.NewTLSObfsServer(conn)
15 | case "http":
16 | c = http.NewHTTPObfsServer(conn)
17 | case "none", "":
18 | c = conn
19 | default:
20 | c = nil
21 | err = fmt.Errorf("invalid obfs type %s", obfs)
22 | }
23 | return
24 | }
25 |
26 | func NewObfsClient(conn net.Conn, server, port, obfs string) (c net.Conn, err error) {
27 | switch obfs {
28 | case "tls":
29 | c = tls.NewTLSObfsClient(conn, server)
30 | case "http":
31 | c = http.NewHTTPObfsClient(conn, server, port)
32 | case "none", "":
33 | c = conn
34 | default:
35 | c = nil
36 | err = fmt.Errorf("invalid obfs type %s", obfs)
37 | }
38 | return
39 | }
40 |
--------------------------------------------------------------------------------
/components/simple-obfs/tls/client.go:
--------------------------------------------------------------------------------
1 | package tls
2 |
3 | // Steal from https://github.com/Dreamacro/clash/component/simple-obfs
4 |
5 | import (
6 | "bytes"
7 | "encoding/binary"
8 | "io"
9 | "math/rand"
10 | "net"
11 | "time"
12 | )
13 |
14 | type TLSObfsClient struct {
15 | net.Conn
16 | server string
17 | remain int
18 | firstRequest bool
19 | firstResponse bool
20 | }
21 |
22 | func (to *TLSObfsClient) read(b []byte, discardN int) (int, error) {
23 | r, n, err := readBlock(to.Conn, b, discardN)
24 | to.remain = r
25 | return n, err
26 | }
27 |
28 | func (to *TLSObfsClient) Read(b []byte) (int, error) {
29 | if to.remain > 0 {
30 | length := to.remain
31 | if length > len(b) {
32 | length = len(b)
33 | }
34 |
35 | n, err := io.ReadFull(to.Conn, b[:length])
36 | to.remain -= n
37 | return n, err
38 | }
39 |
40 | if to.firstResponse {
41 | // type + ver + lensize + 91 = 96
42 | // type + ver + lensize + 1 = 6
43 | // type + ver = 3
44 | to.firstResponse = false
45 | return to.read(b, 105)
46 | }
47 |
48 | // type + ver = 3
49 | return to.read(b, 3)
50 | }
51 | func (to *TLSObfsClient) Write(b []byte) (int, error) {
52 | length := len(b)
53 | for i := 0; i < length; i += chunkSize {
54 | end := i + chunkSize
55 | if end > length {
56 | end = length
57 | }
58 |
59 | n, err := to.write(b[i:end])
60 | if err != nil {
61 | return n, err
62 | }
63 | }
64 | return length, nil
65 | }
66 |
67 | func (to *TLSObfsClient) write(b []byte) (int, error) {
68 | if to.firstRequest {
69 | helloMsg := makeClientHelloMsg(b, to.server)
70 | _, err := to.Conn.Write(helloMsg)
71 | to.firstRequest = false
72 | return len(b), err
73 | }
74 |
75 | buf := bufferPool.Get().(*bytes.Buffer)
76 | buf.Reset()
77 | defer bufferPool.Put(buf)
78 |
79 | buf.Write([]byte{0x17, 0x03, 0x03})
80 | binary.Write(buf, binary.BigEndian, uint16(len(b)))
81 | _, err := to.Conn.Write(buf.Bytes())
82 | if err != nil {
83 | return 0, err
84 | }
85 | return to.Conn.Write(b)
86 | }
87 |
88 | // NewTLSObfsClient return a SimpleObfs
89 | func NewTLSObfsClient(conn net.Conn, server string) net.Conn {
90 | return &TLSObfsClient{
91 | Conn: conn,
92 | server: server,
93 | firstRequest: true,
94 | firstResponse: true,
95 | }
96 | }
97 |
98 | func makeClientHelloMsg(data []byte, server string) []byte {
99 | random := make([]byte, 28)
100 | sessionID := make([]byte, 32)
101 | rand.Read(random)
102 | rand.Read(sessionID)
103 |
104 | buf := bufferPool.Get().(*bytes.Buffer)
105 | buf.Reset()
106 | defer bufferPool.Put(buf)
107 |
108 | // handshake, TLS 1.0 version, length
109 | buf.WriteByte(22)
110 | buf.Write([]byte{0x03, 0x01})
111 | length := uint16(212 + len(data) + len(server))
112 | buf.WriteByte(byte(length >> 8))
113 | buf.WriteByte(byte(length & 0xff))
114 |
115 | // clientHello, length, TLS 1.2 version
116 | buf.WriteByte(1)
117 | buf.WriteByte(0)
118 | binary.Write(buf, binary.BigEndian, uint16(208+len(data)+len(server)))
119 | buf.Write([]byte{0x03, 0x03})
120 |
121 | // random with timestamp, sid len, sid
122 | binary.Write(buf, binary.BigEndian, uint32(time.Now().Unix()))
123 | buf.Write(random)
124 | buf.WriteByte(32)
125 | buf.Write(sessionID)
126 |
127 | // cipher suites
128 | buf.Write([]byte{0x00, 0x38})
129 | buf.Write([]byte{
130 | 0xc0, 0x2c, 0xc0, 0x30, 0x00, 0x9f, 0xcc, 0xa9, 0xcc, 0xa8, 0xcc, 0xaa, 0xc0, 0x2b, 0xc0, 0x2f,
131 | 0x00, 0x9e, 0xc0, 0x24, 0xc0, 0x28, 0x00, 0x6b, 0xc0, 0x23, 0xc0, 0x27, 0x00, 0x67, 0xc0, 0x0a,
132 | 0xc0, 0x14, 0x00, 0x39, 0xc0, 0x09, 0xc0, 0x13, 0x00, 0x33, 0x00, 0x9d, 0x00, 0x9c, 0x00, 0x3d,
133 | 0x00, 0x3c, 0x00, 0x35, 0x00, 0x2f, 0x00, 0xff,
134 | })
135 |
136 | // compression
137 | buf.Write([]byte{0x01, 0x00})
138 |
139 | // extension length
140 | binary.Write(buf, binary.BigEndian, uint16(79+len(data)+len(server)))
141 |
142 | // session ticket
143 | buf.Write([]byte{0x00, 0x23})
144 | binary.Write(buf, binary.BigEndian, uint16(len(data)))
145 | buf.Write(data)
146 |
147 | // server name
148 | buf.Write([]byte{0x00, 0x00})
149 | binary.Write(buf, binary.BigEndian, uint16(len(server)+5))
150 | binary.Write(buf, binary.BigEndian, uint16(len(server)+3))
151 | buf.WriteByte(0)
152 | binary.Write(buf, binary.BigEndian, uint16(len(server)))
153 | buf.Write([]byte(server))
154 |
155 | // ec_point
156 | buf.Write([]byte{0x00, 0x0b, 0x00, 0x04, 0x03, 0x01, 0x00, 0x02})
157 |
158 | // groups
159 | buf.Write([]byte{0x00, 0x0a, 0x00, 0x0a, 0x00, 0x08, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x19, 0x00, 0x18})
160 |
161 | // signature
162 | buf.Write([]byte{
163 | 0x00, 0x0d, 0x00, 0x20, 0x00, 0x1e, 0x06, 0x01, 0x06, 0x02, 0x06, 0x03, 0x05,
164 | 0x01, 0x05, 0x02, 0x05, 0x03, 0x04, 0x01, 0x04, 0x02, 0x04, 0x03, 0x03, 0x01,
165 | 0x03, 0x02, 0x03, 0x03, 0x02, 0x01, 0x02, 0x02, 0x02, 0x03,
166 | })
167 |
168 | // encrypt then mac
169 | buf.Write([]byte{0x00, 0x16, 0x00, 0x00})
170 |
171 | // extended master secret
172 | buf.Write([]byte{0x00, 0x17, 0x00, 0x00})
173 |
174 | return buf.Bytes()
175 | }
176 |
--------------------------------------------------------------------------------
/components/simple-obfs/tls/common.go:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of open-snell.
3 | * open-snell is free software: you can redistribute it and/or modify
4 | * it under the terms of the GNU General Public License as published by
5 | * the Free Software Foundation, either version 3 of the License, or
6 | * (at your option) any later version.
7 | * open-snell is distributed in the hope that it will be useful,
8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 | * GNU General Public License for more details.
11 | * You should have received a copy of the GNU General Public License
12 | * along with open-snell. If not, see .
13 | */
14 |
15 | package tls
16 |
17 | import (
18 | "bytes"
19 | "io"
20 | "math/rand"
21 | "net"
22 | "sync"
23 | "time"
24 |
25 | p "github.com/icpz/open-snell/components/utils/pool"
26 | )
27 |
28 | func init() {
29 | rand.Seed(time.Now().Unix())
30 | }
31 |
32 | const (
33 | chunkSize = 16 * 1024
34 | )
35 |
36 | var bufferPool = sync.Pool{New: func() interface{} { return &bytes.Buffer{} }}
37 |
38 | // read a [length][data...] block
39 | func readBlock(c net.Conn, b []byte, skipSize int) (remain, n int, err error) {
40 | if skipSize > 0 {
41 | buf := p.Get(skipSize)
42 | _, err = io.ReadFull(c, buf)
43 | p.Put(buf)
44 | if err != nil {
45 | return
46 | }
47 | }
48 |
49 | sizeBuf := make([]byte, 2)
50 | _, err = io.ReadFull(c, sizeBuf)
51 | if err != nil {
52 | return
53 | }
54 |
55 | length := (int(sizeBuf[0]) << 8) | int(sizeBuf[1])
56 | if length > len(b) {
57 | n, err = c.Read(b)
58 | remain = length - n
59 | return
60 | }
61 |
62 | n, err = io.ReadFull(c, b[:length])
63 | return
64 | }
65 |
--------------------------------------------------------------------------------
/components/simple-obfs/tls/server.go:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of open-snell.
3 | * open-snell is free software: you can redistribute it and/or modify
4 | * it under the terms of the GNU General Public License as published by
5 | * the Free Software Foundation, either version 3 of the License, or
6 | * (at your option) any later version.
7 | * open-snell is distributed in the hope that it will be useful,
8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 | * GNU General Public License for more details.
11 | * You should have received a copy of the GNU General Public License
12 | * along with open-snell. If not, see .
13 | */
14 |
15 | package tls
16 |
17 | import (
18 | "bytes"
19 | "encoding/binary"
20 | "io"
21 | "math/rand"
22 | "net"
23 | "time"
24 | )
25 |
26 | type TLSObfsServer struct {
27 | net.Conn
28 | remain int
29 | firstRequest bool
30 | sessionTicketDone bool
31 | firstResponse bool
32 | }
33 |
34 | func (tos *TLSObfsServer) read(b []byte, skipSize int) (int, error) {
35 | r, n, err := readBlock(tos.Conn, b, skipSize)
36 | tos.remain = r
37 | return n, err
38 | }
39 |
40 | // skip SNI & other TLS extensions
41 | func (tos *TLSObfsServer) skipOtherExts() error {
42 | // SNI first
43 | buf := make([]byte, 256)
44 | _, err := tos.read(buf, 7)
45 | if err != nil {
46 | return err
47 | }
48 |
49 | _, err = io.ReadFull(tos.Conn, buf[:4*16+2])
50 | return err
51 | }
52 |
53 | func (tos *TLSObfsServer) Read(b []byte) (int, error) {
54 | if tos.remain > 0 {
55 | length := tos.remain
56 | if length > len(b) {
57 | length = len(b)
58 | }
59 |
60 | n, err := io.ReadFull(tos.Conn, b[:length])
61 | tos.remain -= n
62 | return n, err
63 | }
64 |
65 | if tos.firstRequest {
66 | tos.firstRequest = false
67 | return tos.read(b, 9*16-4)
68 | }
69 |
70 | if !tos.sessionTicketDone {
71 | tos.sessionTicketDone = true
72 | err := tos.skipOtherExts()
73 | if err != nil {
74 | return 0, err
75 | }
76 | }
77 |
78 | return tos.read(b, 3)
79 | }
80 |
81 | func (tos *TLSObfsServer) Write(b []byte) (int, error) {
82 | length := len(b)
83 | for i := 0; i < length; i += chunkSize {
84 | end := i + chunkSize
85 | if end > length {
86 | end = length
87 | }
88 |
89 | n, err := tos.write(b[i:end])
90 | if err != nil {
91 | return n, err
92 | }
93 | }
94 | return length, nil
95 | }
96 |
97 | func (tos *TLSObfsServer) write(b []byte) (int, error) {
98 | if tos.firstResponse {
99 | serverHello := makeServerHello(b)
100 | _, err := tos.Conn.Write(serverHello)
101 | tos.firstResponse = false
102 | return len(b), err
103 | }
104 |
105 | buf := bufferPool.Get().(*bytes.Buffer)
106 | buf.Reset()
107 | defer bufferPool.Put(buf)
108 |
109 | buf.Write([]byte{0x17, 0x03, 0x03})
110 | binary.Write(buf, binary.BigEndian, uint16(len(b)))
111 | _, err := tos.Conn.Write(buf.Bytes())
112 | if err != nil {
113 | return 0, err
114 | }
115 | return tos.Conn.Write(b)
116 | }
117 |
118 | func NewTLSObfsServer(conn net.Conn) net.Conn {
119 | return &TLSObfsServer{
120 | Conn: conn,
121 | firstRequest: true,
122 | firstResponse: true,
123 | }
124 | }
125 |
126 | func makeServerHello(data []byte) []byte {
127 | randBytes := make([]byte, 28)
128 | sessionId := make([]byte, 32)
129 |
130 | rand.Read(randBytes)
131 | rand.Read(sessionId)
132 |
133 | buf := bufferPool.Get().(*bytes.Buffer)
134 | buf.Reset()
135 | defer bufferPool.Put(buf)
136 |
137 | buf.WriteByte(0x16)
138 | binary.Write(buf, binary.BigEndian, uint16(0x0301))
139 | binary.Write(buf, binary.BigEndian, uint16(91))
140 | buf.Write([]byte{2, 0, 0, 87, 0x03, 0x03})
141 | binary.Write(buf, binary.BigEndian, uint32(time.Now().Unix()))
142 | buf.Write(randBytes)
143 | buf.WriteByte(32)
144 | buf.Write(sessionId)
145 |
146 | buf.Write([]byte{0xcc, 0xa8})
147 | buf.WriteByte(0)
148 | buf.Write([]byte{0x00, 0x00})
149 | buf.Write([]byte{0xff, 0x01, 0x00, 0x01, 0x00})
150 | buf.Write([]byte{0x00, 0x17, 0x00, 0x00})
151 | buf.Write([]byte{0x00, 0x0b, 0x00, 0x02, 0x01, 0x00})
152 |
153 | buf.Write([]byte{0x14, 0x03, 0x03, 0x00, 0x01, 0x01})
154 |
155 | buf.Write([]byte{0x16, 0x03, 0x03})
156 | binary.Write(buf, binary.BigEndian, uint16(len(data)))
157 | buf.Write(data)
158 |
159 | return buf.Bytes()
160 | }
161 |
--------------------------------------------------------------------------------
/components/snell/client.go:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of open-snell.
3 | * open-snell is free software: you can redistribute it and/or modify
4 | * it under the terms of the GNU General Public License as published by
5 | * the Free Software Foundation, either version 3 of the License, or
6 | * (at your option) any later version.
7 | * open-snell is distributed in the hope that it will be useful,
8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 | * GNU General Public License for more details.
11 | * You should have received a copy of the GNU General Public License
12 | * along with open-snell. If not, see .
13 | */
14 |
15 | package snell
16 |
17 | import (
18 | "bytes"
19 | "encoding/binary"
20 | "errors"
21 | "fmt"
22 | "io"
23 | "net"
24 | "strconv"
25 | "sync"
26 | "time"
27 |
28 | log "github.com/golang/glog"
29 |
30 | "github.com/icpz/open-snell/components/aead"
31 | obfs "github.com/icpz/open-snell/components/simple-obfs"
32 | "github.com/icpz/open-snell/components/socks5"
33 | "github.com/icpz/open-snell/components/utils"
34 | p "github.com/icpz/open-snell/components/utils/pool"
35 | )
36 |
37 | const (
38 | MaxPoolCap = 10
39 | PoolTimeoutMS = 150000
40 | )
41 |
42 | var (
43 | bufferPool = sync.Pool{New: func() interface{} { return &bytes.Buffer{} }}
44 | )
45 |
46 | type clientSession struct {
47 | net.Conn
48 | buffer [1]byte
49 | reply bool
50 | }
51 |
52 | func (s *clientSession) Read(b []byte) (int, error) {
53 | if s.reply {
54 | return s.Conn.Read(b)
55 | }
56 |
57 | s.reply = true
58 | if _, err := io.ReadFull(s.Conn, s.buffer[:]); err != nil {
59 | return 0, err
60 | }
61 |
62 | if s.buffer[0] == ResponseTunnel {
63 | return s.Conn.Read(b)
64 | } else if s.buffer[0] != ResponseError {
65 | return 0, errors.New("Command not support")
66 | }
67 |
68 | // ResponseError
69 | if _, err := io.ReadFull(s.Conn, s.buffer[:]); err != nil {
70 | return 0, err
71 | }
72 | if _, err := io.ReadFull(s.Conn, s.buffer[:]); err != nil {
73 | return 0, err
74 | }
75 |
76 | length := int(s.buffer[0])
77 | msg := make([]byte, length)
78 |
79 | if _, err := io.ReadFull(s.Conn, msg); err != nil {
80 | return 0, err
81 | }
82 |
83 | return 0, NewAppError(0, string(msg))
84 | }
85 |
86 | func WriteHeader(conn net.Conn, host string, port uint, v2 bool) error {
87 | buf := bufferPool.Get().(*bytes.Buffer)
88 | buf.Reset()
89 | defer bufferPool.Put(buf)
90 | buf.WriteByte(Version)
91 | if v2 {
92 | buf.WriteByte(CommandConnectV2)
93 | } else {
94 | buf.WriteByte(CommandConnect)
95 | }
96 |
97 | // clientID length & id
98 | buf.WriteByte(0)
99 |
100 | // host & port
101 | buf.WriteByte(uint8(len(host)))
102 | buf.WriteString(host)
103 | binary.Write(buf, binary.BigEndian, uint16(port))
104 |
105 | if _, err := conn.Write(buf.Bytes()); err != nil {
106 | return err
107 | }
108 |
109 | return nil
110 | }
111 |
112 | type SnellClient struct {
113 | server string
114 | obfs string
115 | obfsHost string
116 | cipher aead.Cipher
117 | socks5 *socks5.SockListener
118 | isV2 bool
119 | pool *snellPool
120 | }
121 |
122 | func (s *SnellClient) StreamConn(c net.Conn, target string) (net.Conn, error) {
123 | host, port, _ := net.SplitHostPort(target)
124 | iport, _ := strconv.Atoi(port)
125 | err := WriteHeader(c, host, uint(iport), s.isV2)
126 | return c, err
127 | }
128 |
129 | func (s *SnellClient) newSession() (net.Conn, error) {
130 | c, err := net.Dial("tcp", s.server)
131 | if err != nil {
132 | return nil, err
133 | }
134 |
135 | if tc, ok := c.(*net.TCPConn); ok {
136 | tc.SetKeepAlive(true)
137 | }
138 |
139 | _, port, _ := net.SplitHostPort(s.server)
140 | c, _ = obfs.NewObfsClient(c, s.obfsHost, port, s.obfs)
141 |
142 | c = &clientSession{
143 | Conn: aead.NewConn(c, s.cipher),
144 | }
145 |
146 | return c, nil
147 | }
148 |
149 | func (s *SnellClient) GetSession(target string) (net.Conn, error) {
150 | c, err := s.pool.Get()
151 | if err != nil {
152 | return nil, err
153 | }
154 | log.V(1).Infof("Using conn %s\n", c.LocalAddr().String())
155 | c, err = s.StreamConn(c, target)
156 | if err != nil {
157 | s.DropSession(c)
158 | return nil, err
159 | }
160 | return c, nil
161 | }
162 |
163 | func (s *SnellClient) PutSession(c net.Conn) {
164 | if pc, ok := c.(*snellPoolConn); ok {
165 | pc.Conn.(*clientSession).reply = false
166 | } else {
167 | log.Fatalf("Invalid session type!")
168 | }
169 |
170 | if !s.isV2 {
171 | s.DropSession(c)
172 | } else {
173 | log.V(1).Infof("Cache conn %s\n", c.LocalAddr().String())
174 | c.Close()
175 | }
176 | }
177 |
178 | func (s *SnellClient) DropSession(c net.Conn) {
179 | if sess, ok := c.(*snellPoolConn); ok {
180 | sess.MarkUnusable()
181 | sess.Close()
182 | } else {
183 | log.Fatalf("Invalid session type!")
184 | }
185 | }
186 |
187 | func (s *SnellClient) Close() {
188 | s.socks5.Close()
189 | s.pool.Close()
190 | }
191 |
192 | func NewSnellClient(listen, server, obfs, obfsHost, psk string, isV2 bool) (*SnellClient, error) {
193 | if obfs != "tls" && obfs != "http" && obfs != "" {
194 | return nil, fmt.Errorf("invalid snell obfs type %s", obfs)
195 | }
196 |
197 | if obfsHost == "" {
198 | obfsHost = "www.bing.com"
199 | }
200 |
201 | var cipher aead.Cipher = nil
202 | if isV2 {
203 | cipher = aead.NewAES128GCM([]byte(psk))
204 | } else {
205 | cipher = aead.NewChacha20Poly1305([]byte(psk))
206 | }
207 | sc := &SnellClient{
208 | server: server,
209 | obfs: obfs,
210 | obfsHost: obfsHost,
211 | cipher: cipher,
212 | isV2: isV2,
213 | }
214 |
215 | p, err := newSnellPool(MaxPoolCap, PoolTimeoutMS, sc.newSession)
216 | if err != nil {
217 | return nil, err
218 | }
219 | sc.pool = p
220 |
221 | sl, err := socks5.NewSocksProxy(listen, sc.handleSnell)
222 | if err != nil {
223 | return nil, err
224 | }
225 | sc.socks5 = sl
226 |
227 | return sc, nil
228 | }
229 |
230 | func (s *SnellClient) handleSnell(client net.Conn, addr socks5.Addr) {
231 | target, err := s.GetSession(addr.String())
232 | log.V(1).Infof("New target from %s to %s\n", client.RemoteAddr().String(), addr.String())
233 | if err != nil {
234 | log.Warningf("Failed to connect to target %s, error %v\n", addr.String(), err)
235 | client.Close()
236 | return
237 | }
238 |
239 | _, er := utils.Relay(client, target)
240 |
241 | client.Close()
242 | if s.isV2 {
243 | target.SetReadDeadline(time.Time{})
244 | _, err := target.Write([]byte{}) // write zero chunk back
245 | if err != nil {
246 | log.Errorf("Unexpected write error %v\n", err)
247 | s.DropSession(target)
248 | return
249 | }
250 | switch e := er.(type) {
251 | case *net.OpError:
252 | if e.Op == "write" {
253 | log.V(1).Infof("Ignored write error %v\n", e)
254 | er = nil
255 | } else if ae, ok := e.Unwrap().(*AppError); ok {
256 | log.Errorf("Server reported error: %v\n", ae)
257 | er = nil
258 | }
259 | }
260 | buf := p.Get(p.RelayBufferSize)
261 | for er == nil {
262 | _, err := target.Read(buf)
263 | er = err
264 | }
265 | p.Put(buf)
266 | if !errors.Is(er, aead.ErrZeroChunk) {
267 | log.Warningf("Unexpected error %v, ZERO CHUNK wanted\n", er)
268 | s.DropSession(target)
269 | return
270 | }
271 | }
272 | s.PutSession(target)
273 |
274 | log.V(1).Infof("Session from %s done\n", client.RemoteAddr().String())
275 | }
276 |
--------------------------------------------------------------------------------
/components/snell/common.go:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of open-snell.
3 | * open-snell is free software: you can redistribute it and/or modify
4 | * it under the terms of the GNU General Public License as published by
5 | * the Free Software Foundation, either version 3 of the License, or
6 | * (at your option) any later version.
7 | * open-snell is distributed in the hope that it will be useful,
8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 | * GNU General Public License for more details.
11 | * You should have received a copy of the GNU General Public License
12 | * along with open-snell. If not, see .
13 | */
14 |
15 | package snell
16 |
17 | const (
18 | CommandPing byte = 0
19 | CommandConnect byte = 1
20 | CommandConnectV2 byte = 5
21 | CommandUDP byte = 6
22 |
23 | CommandUDPForward byte = 1
24 |
25 | ResponseTunnel byte = 0
26 | ResponseReady byte = 0
27 | ResponsePong byte = 1
28 | ResponseError byte = 2
29 |
30 | Version byte = 1
31 | )
32 |
33 | type AppError struct {
34 | code byte
35 | msg string
36 | }
37 |
38 | func (e *AppError) Error() string {
39 | return e.msg
40 | }
41 |
42 | func NewAppError(code byte, msg string) error {
43 | return &AppError{
44 | code: code,
45 | msg: msg,
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/components/snell/fastopen.go:
--------------------------------------------------------------------------------
1 | //go:build !linux
2 |
3 | package snell
4 |
5 | import (
6 | "net"
7 | )
8 |
9 | func setTcpFastOpen(lis net.Listener, enable int) error {
10 | return nil
11 | }
12 |
--------------------------------------------------------------------------------
/components/snell/fastopen_linux.go:
--------------------------------------------------------------------------------
1 |
2 | package snell
3 |
4 | import (
5 | "errors"
6 | "net"
7 | "syscall"
8 |
9 | log "github.com/golang/glog"
10 | )
11 |
12 | func setTcpFastOpen(lis net.Listener, enable int) error {
13 | if tl, ok := lis.(*net.TCPListener); ok {
14 | file, err := tl.File()
15 | if err != nil {
16 | return err
17 | }
18 | sysconn, err := file.SyscallConn()
19 | if err != nil {
20 | return err
21 | }
22 | return sysconn.Control(func(fd uintptr) {
23 | if err := syscall.SetsockoptInt(int(fd), syscall.SOL_TCP, 23, enable); err != nil {
24 | log.Warningf("failed to set TCP fastopen: %v\n", err)
25 | }
26 | })
27 | }
28 | return errors.New("invalid listener")
29 | }
30 |
--------------------------------------------------------------------------------
/components/snell/pool.go:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of open-snell.
3 | * open-snell is free software: you can redistribute it and/or modify
4 | * it under the terms of the GNU General Public License as published by
5 | * the Free Software Foundation, either version 3 of the License, or
6 | * (at your option) any later version.
7 | * open-snell is distributed in the hope that it will be useful,
8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 | * GNU General Public License for more details.
11 | * You should have received a copy of the GNU General Public License
12 | * along with open-snell. If not, see .
13 | */
14 |
15 | package snell
16 |
17 | import (
18 | "context"
19 | "errors"
20 | "net"
21 |
22 | "github.com/icpz/pool"
23 | )
24 |
25 | type snellFactory = func() (net.Conn, error)
26 |
27 | type snellPool struct {
28 | pool *pool.Pool
29 | }
30 |
31 | func (p *snellPool) Get() (net.Conn, error) {
32 | i := p.pool.Get()
33 | switch e := i.(type) {
34 | case error:
35 | return nil, e
36 | case net.Conn:
37 | return &snellPoolConn{
38 | Conn: e,
39 | pool: p,
40 | }, nil
41 | }
42 | return nil, errors.New("Invalid Type")
43 | }
44 |
45 | func (p *snellPool) Close() {
46 | p.pool.ReleaseAll()
47 | }
48 |
49 | type snellPoolConn struct {
50 | net.Conn
51 | pool *snellPool
52 | }
53 |
54 | func (pc *snellPoolConn) Close() error {
55 | if pc.pool == nil {
56 | return pc.Conn.Close()
57 | }
58 | pc.pool.pool.Put(pc.Conn)
59 | return nil
60 | }
61 |
62 | func (pc *snellPoolConn) MarkUnusable() {
63 | pc.pool = nil
64 | }
65 |
66 | func newSnellPool(maxSize, leaseMS int, factory snellFactory) (*snellPool, error) {
67 | p := pool.New(
68 | func(ctx context.Context) interface{} {
69 | c, e := factory()
70 | if e != nil {
71 | return e
72 | }
73 | return c
74 | },
75 | pool.OptCapacity(maxSize),
76 | pool.OptLeaseMS(int64(leaseMS)),
77 | pool.OptDeleter(func(i interface{}) {
78 | if c, ok := i.(net.Conn); ok {
79 | c.Close()
80 | }
81 | }),
82 | )
83 | return &snellPool{p}, nil
84 | }
85 |
--------------------------------------------------------------------------------
/components/snell/server.go:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of open-snell.
3 | * open-snell is free software: you can redistribute it and/or modify
4 | * it under the terms of the GNU General Public License as published by
5 | * the Free Software Foundation, either version 3 of the License, or
6 | * (at your option) any later version.
7 | * open-snell is distributed in the hope that it will be useful,
8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 | * GNU General Public License for more details.
11 | * You should have received a copy of the GNU General Public License
12 | * along with open-snell. If not, see .
13 | */
14 |
15 | package snell
16 |
17 | import (
18 | "bytes"
19 | "errors"
20 | "fmt"
21 | "io"
22 | "net"
23 | "strconv"
24 | "syscall"
25 | "time"
26 |
27 | log "github.com/golang/glog"
28 | lru "github.com/hashicorp/golang-lru"
29 |
30 | "github.com/icpz/open-snell/components/aead"
31 | obfs "github.com/icpz/open-snell/components/simple-obfs"
32 | "github.com/icpz/open-snell/components/utils"
33 | p "github.com/icpz/open-snell/components/utils/pool"
34 | )
35 |
36 | type SnellServer struct {
37 | listener net.Listener
38 | psk []byte
39 | closed bool
40 | }
41 |
42 | func (s *SnellServer) ServerHandshake(c net.Conn) (target string, cmd byte, err error) {
43 | buf := make([]byte, 255)
44 | if _, err = io.ReadFull(c, buf[:3]); err != nil {
45 | return
46 | }
47 |
48 | if buf[0] != Version {
49 | log.Warningf("invalid snell version %x\n", buf[0])
50 | return
51 | }
52 |
53 | cmd = buf[1]
54 | clen := buf[2]
55 | if clen > 0 {
56 | if _, err = io.ReadFull(c, buf[:clen]); err != nil {
57 | return
58 | }
59 |
60 | log.V(1).Infof("client id %s\n", string(buf[:clen]))
61 | }
62 |
63 | if cmd == CommandUDP {
64 | log.V(1).Infof("UDP request, skip reading in handshake stage\n")
65 | return
66 | }
67 |
68 | if _, err = io.ReadFull(c, buf[:1]); err != nil {
69 | return
70 | }
71 | hlen := buf[0]
72 | if _, err = io.ReadFull(c, buf[:hlen+2]); err != nil {
73 | return
74 | }
75 | host := string(buf[:hlen])
76 | port := strconv.Itoa((int(buf[hlen]) << 8) | int(buf[hlen+1]))
77 | target = net.JoinHostPort(host, port)
78 | return
79 | }
80 |
81 | func (s *SnellServer) Close() {
82 | s.closed = true
83 | s.listener.Close()
84 | }
85 |
86 | func NewSnellServer(listen, psk, obfsType string) (*SnellServer, error) {
87 | if obfsType != "tls" && obfsType != "http" && obfsType != "" {
88 | return nil, fmt.Errorf("invalid snell obfs type %s", obfsType)
89 | }
90 |
91 | l, err := net.Listen("tcp", listen)
92 | if err != nil {
93 | return nil, err
94 | }
95 | setTcpFastOpen(l, 1)
96 |
97 | bpsk := []byte(psk)
98 | ss := &SnellServer{l, bpsk, false}
99 | ciph := aead.NewAES128GCM(bpsk)
100 | fb := aead.NewChacha20Poly1305(bpsk)
101 | go func() {
102 | log.Infof("snell server listening at: %s\n", listen)
103 | for {
104 | c, err := l.Accept()
105 | if err != nil {
106 | if ss.closed {
107 | break
108 | }
109 | continue
110 | }
111 | c, _ = obfs.NewObfsServer(c, obfsType)
112 | c = aead.NewConnWithFallback(c, ciph, fb)
113 | go ss.handleSnell(c)
114 | }
115 | }()
116 |
117 | return ss, nil
118 | }
119 |
120 | func (s *SnellServer) handleSnell(conn net.Conn) {
121 | defer conn.Close()
122 |
123 | isV2 := true
124 |
125 | muxLoop:
126 | for isV2 {
127 | target, command, err := s.ServerHandshake(conn)
128 | if err != nil {
129 | if err != io.EOF {
130 | log.Warningf("Failed to handshake from %s: %v\n", conn.RemoteAddr().String(), err)
131 | }
132 | break
133 | }
134 |
135 | if command != CommandUDP {
136 | log.V(1).Infof("New target from %s to %s\n", conn.RemoteAddr().String(), target)
137 | }
138 |
139 | if c, ok := conn.(*net.TCPConn); ok {
140 | c.SetKeepAlive(true)
141 | }
142 |
143 | if command == CommandPing {
144 | buf := []byte{ResponsePong}
145 | conn.Write(buf)
146 | break
147 | }
148 |
149 | switch command {
150 | case CommandConnect:
151 | isV2 = false
152 | case CommandUDP:
153 | s.handleUDPRequest(conn)
154 | break muxLoop
155 | case CommandConnectV2:
156 | default:
157 | log.Errorf("Unknown command 0x%x\n", command)
158 | break muxLoop
159 | }
160 |
161 | var el error = nil
162 | tc, err := net.Dial("tcp", target)
163 | if err != nil {
164 | el = s.writeError(conn, err)
165 | } else {
166 | defer tc.Close()
167 | _, el = conn.Write([]byte{ResponseTunnel})
168 | if el != nil {
169 | log.Errorf("Failed to write ResponseTunnel: %v\n", el)
170 | } else {
171 | el, _ = utils.Relay(conn, tc)
172 | }
173 | }
174 |
175 | if isV2 {
176 | conn.SetReadDeadline(time.Time{})
177 | _, err := conn.Write([]byte{}) // write zero chunk back
178 | if err != nil {
179 | log.Errorf("Unexpected write error %v\n", err)
180 | return
181 | }
182 | if e, ok := el.(*net.OpError); ok {
183 | if e.Op == "write" {
184 | el = nil
185 | }
186 | }
187 | buf := p.Get(p.RelayBufferSize)
188 | for el == nil {
189 | _, err := conn.Read(buf)
190 | el = err
191 | }
192 | p.Put(buf)
193 | if !errors.Is(el, aead.ErrZeroChunk) {
194 | if !errors.Is(el, io.EOF) {
195 | log.Warningf("Unexpected error %v, ZERO CHUNK wanted\n", el)
196 | }
197 | log.V(1).Infof("Close connection due to %v anyway\n", el)
198 | break
199 | }
200 | }
201 | }
202 |
203 | log.V(1).Infof("Session from %s done", conn.RemoteAddr().String())
204 | }
205 |
206 | func (s *SnellServer) writeError(conn net.Conn, err error) error {
207 | buf := bytes.NewBuffer([]byte{})
208 | buf.WriteByte(ResponseError)
209 | if e, ok := err.(syscall.Errno); ok {
210 | buf.WriteByte(byte(e))
211 | } else {
212 | buf.WriteByte(byte(0))
213 | }
214 | es := err.Error()
215 | if len(es) > 250 {
216 | es = es[0:250]
217 | }
218 | buf.WriteByte(byte(len(es)))
219 | buf.WriteString(es)
220 | _, el := conn.Write(buf.Bytes())
221 | return el
222 | }
223 |
224 | func (s *SnellServer) handleUDPRequest(conn net.Conn) {
225 | log.V(1).Infof("New UDP request from %s\n", conn.RemoteAddr().String())
226 |
227 | cache, err := lru.New(256)
228 | if err != nil {
229 | log.Errorf("UDP failed to create lru cache: %v\n", err)
230 | return
231 | }
232 | defer cache.Purge()
233 |
234 | pc, err := net.ListenPacket("udp", "0.0.0.0:0")
235 | if err != nil {
236 | log.Errorf("UDP failed to listen: %v\n", err)
237 | s.writeError(conn, err)
238 | return
239 | } else {
240 | defer pc.Close()
241 | log.V(1).Infof("UDP listening on: %s\n", pc.LocalAddr().String())
242 | if _, err := conn.Write([]byte{ResponseReady}); err != nil {
243 | log.Errorf("Failed to write ResponseReady: %v\n", err)
244 | return
245 | }
246 | }
247 |
248 | go s.handleUDPIngress(conn, pc)
249 |
250 | buf := p.Get(p.RelayBufferSize)
251 | defer p.Put(buf)
252 |
253 | uotLoop:
254 | for {
255 | n, err := conn.Read(buf)
256 | if err != nil {
257 | if errors.Is(err, io.EOF) {
258 | log.V(1).Infof("UDP over TCP read EOF, session ends\n")
259 | } else {
260 | log.Errorf("UDP over TCP read error: %v\n", err)
261 | }
262 | break
263 | }
264 |
265 | if n < 5 {
266 | log.Errorf("UDP over TCP insufficient chunk size: %d < 5\n", n)
267 | break
268 | }
269 | cmd := buf[0]
270 | hlen := buf[1]
271 | iplen := 0
272 | head := 2
273 | host := ""
274 |
275 | if cmd != CommandUDPForward {
276 | log.Errorf("UDP over TCP unknown UDP command: 0x%x\n", cmd)
277 | break
278 | }
279 | if hlen == 0 {
280 | switch buf[2] {
281 | case 4:
282 | iplen = 4
283 | case 6:
284 | iplen = 16
285 | default:
286 | log.Errorf("Unknown IP Version: 0x%x\n", buf[2])
287 | break uotLoop
288 | }
289 |
290 | head = 3 + iplen /* now points to port */
291 | if n < head + 2 {
292 | log.Errorf("UDP over TCP insufficient chunk size: %d < %d\n", n, head + 2)
293 | break
294 | }
295 | ip := net.IP(buf[3:head])
296 | host = ip.String()
297 | } else {
298 | head = 2 + int(hlen)
299 | if n < head + 2 {
300 | log.Errorf("UDP over TCP insufficient chunk size: %d < %d\n", n, head + 2)
301 | break
302 | }
303 | host = string(buf[2:head])
304 | }
305 | port := (int(buf[head]) << 8) | int(buf[head+1])
306 | head += 2
307 | target := net.JoinHostPort(host, strconv.Itoa(port))
308 | log.V(1).Infof("UDP over TCP forwarding to %s\n", target)
309 |
310 | var uaddr *net.UDPAddr
311 | if value, ok := cache.Get(target); ok {
312 | uaddr = value.(*net.UDPAddr)
313 | log.V(1).Infof("UDP cache hit: %s -> %s\n", target, uaddr.String())
314 | } else {
315 | uaddr, err = net.ResolveUDPAddr("udp", target)
316 | if err != nil {
317 | log.Warningf("UDP over TCP failed to resolve %s: %v\n", target, err)
318 | /* won't close connection, but cause this packet losses */
319 | }
320 | log.V(1).Infof("UDP over TCP resolved target %s -> %s\n", target, uaddr.String())
321 | cache.Add(target, uaddr)
322 | }
323 |
324 | payloadSize := n - head
325 | if payloadSize > 0 {
326 | log.V(1).Infof("UDP over TCP forward %d bytes to target %s\n", payloadSize, target)
327 | _, err = pc.WriteTo(buf[head:n], uaddr)
328 | if err != nil {
329 | log.Errorf("UDP over TCP failed to write to %s: %v\n", target, err)
330 | break
331 | }
332 | }
333 | }
334 | }
335 |
336 | func (s *SnellServer) handleUDPIngress(conn net.Conn, pc net.PacketConn) {
337 | buf := p.Get(p.RelayBufferSize)
338 | defer p.Put(buf)
339 |
340 | for {
341 | n, raddr, err := pc.ReadFrom(buf)
342 | if err != nil {
343 | if !errors.Is(err, net.ErrClosed) {
344 | log.Errorf("UDP failed to read: %v\n", err)
345 | }
346 | break
347 | }
348 | log.V(1).Infof("UDP read %d bytes from %s\n", n, raddr.String())
349 |
350 | uaddr := raddr.(*net.UDPAddr)
351 | ipver := 4
352 | if uaddr.IP.To4() == nil {
353 | ipver = 6
354 | }
355 | buffer := bytes.NewBuffer([]byte{})
356 | buffer.WriteByte(byte(ipver))
357 | switch ipver {
358 | case 4:
359 | buffer.Write([]byte(uaddr.IP.To4()))
360 | case 6:
361 | buffer.Write([]byte(uaddr.IP.To16()))
362 | }
363 | buffer.Write([]byte{byte(uaddr.Port>>8), byte(uaddr.Port&0xff)})
364 | buffer.Write(buf[:n])
365 |
366 | _, err = conn.Write(buffer.Bytes())
367 | if err != nil {
368 | log.Errorf("UDP failed to write back: %v\n", err)
369 | break
370 | }
371 | }
372 | }
373 |
--------------------------------------------------------------------------------
/components/socks5/protocol.go:
--------------------------------------------------------------------------------
1 | package socks5
2 |
3 | // Steal from github.com/Dreamacro/clash/component/socks5
4 |
5 | import (
6 | "bytes"
7 | "encoding/binary"
8 | "errors"
9 | "io"
10 | "net"
11 | "strconv"
12 | )
13 |
14 | // Error represents a SOCKS error
15 | type Error byte
16 |
17 | func (err Error) Error() string {
18 | return "SOCKS error: " + strconv.Itoa(int(err))
19 | }
20 |
21 | // Command is request commands as defined in RFC 1928 section 4.
22 | type Command = uint8
23 |
24 | // SOCKS request commands as defined in RFC 1928 section 4.
25 | const (
26 | CmdConnect Command = 1
27 | CmdBind Command = 2
28 | CmdUDPAssociate Command = 3
29 | )
30 |
31 | // SOCKS address types as defined in RFC 1928 section 5.
32 | const (
33 | AtypIPv4 = 1
34 | AtypDomainName = 3
35 | AtypIPv6 = 4
36 | )
37 |
38 | // MaxAddrLen is the maximum size of SOCKS address in bytes.
39 | const MaxAddrLen = 1 + 1 + 255 + 2
40 |
41 | // Addr represents a SOCKS address as defined in RFC 1928 section 5.
42 | type Addr []byte
43 |
44 | func (a Addr) String() string {
45 | var host, port string
46 |
47 | switch a[0] {
48 | case AtypDomainName:
49 | hostLen := uint16(a[1])
50 | host = string(a[2 : 2+hostLen])
51 | port = strconv.Itoa((int(a[2+hostLen]) << 8) | int(a[2+hostLen+1]))
52 | case AtypIPv4:
53 | host = net.IP(a[1 : 1+net.IPv4len]).String()
54 | port = strconv.Itoa((int(a[1+net.IPv4len]) << 8) | int(a[1+net.IPv4len+1]))
55 | case AtypIPv6:
56 | host = net.IP(a[1 : 1+net.IPv6len]).String()
57 | port = strconv.Itoa((int(a[1+net.IPv6len]) << 8) | int(a[1+net.IPv6len+1]))
58 | }
59 |
60 | return net.JoinHostPort(host, port)
61 | }
62 |
63 | // UDPAddr converts a socks5.Addr to *net.UDPAddr
64 | func (a Addr) UDPAddr() *net.UDPAddr {
65 | if len(a) == 0 {
66 | return nil
67 | }
68 | switch a[0] {
69 | case AtypIPv4:
70 | var ip [net.IPv4len]byte
71 | copy(ip[0:], a[1:1+net.IPv4len])
72 | return &net.UDPAddr{IP: net.IP(ip[:]), Port: int(binary.BigEndian.Uint16(a[1+net.IPv4len : 1+net.IPv4len+2]))}
73 | case AtypIPv6:
74 | var ip [net.IPv6len]byte
75 | copy(ip[0:], a[1:1+net.IPv6len])
76 | return &net.UDPAddr{IP: net.IP(ip[:]), Port: int(binary.BigEndian.Uint16(a[1+net.IPv6len : 1+net.IPv6len+2]))}
77 | }
78 | // Other Atyp
79 | return nil
80 | }
81 |
82 | // SOCKS errors as defined in RFC 1928 section 6.
83 | const (
84 | ErrGeneralFailure = Error(1)
85 | ErrConnectionNotAllowed = Error(2)
86 | ErrNetworkUnreachable = Error(3)
87 | ErrHostUnreachable = Error(4)
88 | ErrConnectionRefused = Error(5)
89 | ErrTTLExpired = Error(6)
90 | ErrCommandNotSupported = Error(7)
91 | ErrAddressNotSupported = Error(8)
92 | )
93 |
94 | // ServerHandshake fast-tracks SOCKS initialization to get target address to connect on server side.
95 | func ServerHandshake(rw net.Conn) (addr Addr, command Command, err error) {
96 | // Read RFC 1928 for request and reply structure and sizes.
97 | buf := make([]byte, MaxAddrLen)
98 | // read VER, NMETHODS, METHODS
99 | if _, err = io.ReadFull(rw, buf[:2]); err != nil {
100 | return
101 | }
102 | nmethods := buf[1]
103 | if _, err = io.ReadFull(rw, buf[:nmethods]); err != nil {
104 | return
105 | }
106 |
107 | if _, err = rw.Write([]byte{5, 0}); err != nil {
108 | return
109 | }
110 |
111 | // read VER CMD RSV ATYP DST.ADDR DST.PORT
112 | if _, err = io.ReadFull(rw, buf[:3]); err != nil {
113 | return
114 | }
115 |
116 | command = buf[1]
117 | addr, err = ReadAddr(rw, buf)
118 | if err != nil {
119 | return
120 | }
121 |
122 | switch command {
123 | case CmdConnect, CmdUDPAssociate:
124 | // Acquire server listened address info
125 | localAddr := ParseAddr(rw.LocalAddr().String())
126 | if localAddr == nil {
127 | err = ErrAddressNotSupported
128 | } else {
129 | // write VER REP RSV ATYP BND.ADDR BND.PORT
130 | _, err = rw.Write(bytes.Join([][]byte{{5, 0, 0}, localAddr}, []byte{}))
131 | }
132 | case CmdBind:
133 | fallthrough
134 | default:
135 | err = ErrCommandNotSupported
136 | }
137 |
138 | return
139 | }
140 |
141 | // ClientHandshake fast-tracks SOCKS initialization to get target address to connect on client side.
142 | func ClientHandshake(rw io.ReadWriter, addr Addr, command Command) (Addr, error) {
143 | buf := make([]byte, MaxAddrLen)
144 | var err error
145 |
146 | // VER, NMETHODS, METHODS
147 | _, err = rw.Write([]byte{5, 1, 0})
148 | if err != nil {
149 | return nil, err
150 | }
151 |
152 | // VER, METHOD
153 | if _, err := io.ReadFull(rw, buf[:2]); err != nil {
154 | return nil, err
155 | }
156 |
157 | if buf[0] != 5 {
158 | return nil, errors.New("SOCKS version error")
159 | }
160 |
161 | if buf[1] != 0 {
162 | return nil, errors.New("SOCKS need auth")
163 | }
164 |
165 | // VER, CMD, RSV, ADDR
166 | if _, err := rw.Write(bytes.Join([][]byte{{5, command, 0}, addr}, []byte{})); err != nil {
167 | return nil, err
168 | }
169 |
170 | // VER, REP, RSV
171 | if _, err := io.ReadFull(rw, buf[:3]); err != nil {
172 | return nil, err
173 | }
174 |
175 | return ReadAddr(rw, buf)
176 | }
177 |
178 | func ReadAddr(r io.Reader, b []byte) (Addr, error) {
179 | if len(b) < MaxAddrLen {
180 | return nil, io.ErrShortBuffer
181 | }
182 | _, err := io.ReadFull(r, b[:1]) // read 1st byte for address type
183 | if err != nil {
184 | return nil, err
185 | }
186 |
187 | switch b[0] {
188 | case AtypDomainName:
189 | _, err = io.ReadFull(r, b[1:2]) // read 2nd byte for domain length
190 | if err != nil {
191 | return nil, err
192 | }
193 | domainLength := uint16(b[1])
194 | _, err = io.ReadFull(r, b[2:2+domainLength+2])
195 | return b[:1+1+domainLength+2], err
196 | case AtypIPv4:
197 | _, err = io.ReadFull(r, b[1:1+net.IPv4len+2])
198 | return b[:1+net.IPv4len+2], err
199 | case AtypIPv6:
200 | _, err = io.ReadFull(r, b[1:1+net.IPv6len+2])
201 | return b[:1+net.IPv6len+2], err
202 | }
203 |
204 | return nil, ErrAddressNotSupported
205 | }
206 |
207 | // SplitAddr slices a SOCKS address from beginning of b. Returns nil if failed.
208 | func SplitAddr(b []byte) Addr {
209 | addrLen := 1
210 | if len(b) < addrLen {
211 | return nil
212 | }
213 |
214 | switch b[0] {
215 | case AtypDomainName:
216 | if len(b) < 2 {
217 | return nil
218 | }
219 | addrLen = 1 + 1 + int(b[1]) + 2
220 | case AtypIPv4:
221 | addrLen = 1 + net.IPv4len + 2
222 | case AtypIPv6:
223 | addrLen = 1 + net.IPv6len + 2
224 | default:
225 | return nil
226 |
227 | }
228 |
229 | if len(b) < addrLen {
230 | return nil
231 | }
232 |
233 | return b[:addrLen]
234 | }
235 |
236 | // ParseAddr parses the address in string s. Returns nil if failed.
237 | func ParseAddr(s string) Addr {
238 | var addr Addr
239 | host, port, err := net.SplitHostPort(s)
240 | if err != nil {
241 | return nil
242 | }
243 | if ip := net.ParseIP(host); ip != nil {
244 | if ip4 := ip.To4(); ip4 != nil {
245 | addr = make([]byte, 1+net.IPv4len+2)
246 | addr[0] = AtypIPv4
247 | copy(addr[1:], ip4)
248 | } else {
249 | addr = make([]byte, 1+net.IPv6len+2)
250 | addr[0] = AtypIPv6
251 | copy(addr[1:], ip)
252 | }
253 | } else {
254 | if len(host) > 255 {
255 | return nil
256 | }
257 | addr = make([]byte, 1+1+len(host)+2)
258 | addr[0] = AtypDomainName
259 | addr[1] = byte(len(host))
260 | copy(addr[2:], host)
261 | }
262 |
263 | portnum, err := strconv.ParseUint(port, 10, 16)
264 | if err != nil {
265 | return nil
266 | }
267 |
268 | addr[len(addr)-2], addr[len(addr)-1] = byte(portnum>>8), byte(portnum)
269 |
270 | return addr
271 | }
272 |
273 | // ParseAddrToSocksAddr parse a socks addr from net.addr
274 | // This is a fast path of ParseAddr(addr.String())
275 | func ParseAddrToSocksAddr(addr net.Addr) Addr {
276 | var hostip net.IP
277 | var port int
278 | if udpaddr, ok := addr.(*net.UDPAddr); ok {
279 | hostip = udpaddr.IP
280 | port = udpaddr.Port
281 | } else if tcpaddr, ok := addr.(*net.TCPAddr); ok {
282 | hostip = tcpaddr.IP
283 | port = tcpaddr.Port
284 | }
285 |
286 | // fallback parse
287 | if hostip == nil {
288 | return ParseAddr(addr.String())
289 | }
290 |
291 | var parsed Addr
292 | if ip4 := hostip.To4(); ip4.DefaultMask() != nil {
293 | parsed = make([]byte, 1+net.IPv4len+2)
294 | parsed[0] = AtypIPv4
295 | copy(parsed[1:], ip4)
296 | binary.BigEndian.PutUint16(parsed[1+net.IPv4len:], uint16(port))
297 |
298 | } else {
299 | parsed = make([]byte, 1+net.IPv6len+2)
300 | parsed[0] = AtypIPv6
301 | copy(parsed[1:], hostip)
302 | binary.BigEndian.PutUint16(parsed[1+net.IPv6len:], uint16(port))
303 | }
304 | return parsed
305 | }
306 |
307 | // DecodeUDPPacket split `packet` to addr payload, and this function is mutable with `packet`
308 | func DecodeUDPPacket(packet []byte) (addr Addr, payload []byte, err error) {
309 | if len(packet) < 5 {
310 | err = errors.New("insufficient length of packet")
311 | return
312 | }
313 |
314 | // packet[0] and packet[1] are reserved
315 | if !bytes.Equal(packet[:2], []byte{0, 0}) {
316 | err = errors.New("reserved fields should be zero")
317 | return
318 | }
319 |
320 | if packet[2] != 0 /* fragments */ {
321 | err = errors.New("discarding fragmented payload")
322 | return
323 | }
324 |
325 | addr = SplitAddr(packet[3:])
326 | if addr == nil {
327 | err = errors.New("failed to read UDP header")
328 | }
329 |
330 | payload = packet[3+len(addr):]
331 | return
332 | }
333 |
334 | func EncodeUDPPacket(addr Addr, payload []byte) (packet []byte, err error) {
335 | if addr == nil {
336 | err = errors.New("address is invalid")
337 | return
338 | }
339 | packet = bytes.Join([][]byte{{0, 0, 0}, addr, payload}, []byte{})
340 | return
341 | }
342 |
--------------------------------------------------------------------------------
/components/socks5/server.go:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of open-snell.
3 | * open-snell is free software: you can redistribute it and/or modify
4 | * it under the terms of the GNU General Public License as published by
5 | * the Free Software Foundation, either version 3 of the License, or
6 | * (at your option) any later version.
7 | * open-snell is distributed in the hope that it will be useful,
8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 | * GNU General Public License for more details.
11 | * You should have received a copy of the GNU General Public License
12 | * along with open-snell. If not, see .
13 | */
14 |
15 | package socks5
16 |
17 | import (
18 | "io"
19 | "io/ioutil"
20 | "net"
21 |
22 | log "github.com/golang/glog"
23 | )
24 |
25 | type SocksCallback func(net.Conn, Addr)
26 |
27 | type SockListener struct {
28 | net.Listener
29 | address string
30 | closed bool
31 | callback SocksCallback
32 | }
33 |
34 | func NewSocksProxy(addr string, cb SocksCallback) (*SockListener, error) {
35 | l, err := net.Listen("tcp", addr)
36 | if err != nil {
37 | return nil, err
38 | }
39 |
40 | sl := &SockListener{l, addr, false, cb}
41 | go func() {
42 | log.Infof("SOCKS proxy listening at: %s\n", addr)
43 | for {
44 | c, err := l.Accept()
45 | if err != nil {
46 | if sl.closed {
47 | break
48 | }
49 | continue
50 | }
51 | go handleSocks(c, sl.callback)
52 | }
53 | }()
54 |
55 | return sl, nil
56 | }
57 |
58 | func (l *SockListener) Close() {
59 | l.closed = true
60 | l.Listener.Close()
61 | }
62 |
63 | func (l *SockListener) Address() string {
64 | return l.address
65 | }
66 |
67 | func handleSocks(conn net.Conn, cb SocksCallback) {
68 | target, command, err := ServerHandshake(conn)
69 | if err != nil {
70 | conn.Close()
71 | return
72 | }
73 | if c, ok := conn.(*net.TCPConn); ok {
74 | c.SetKeepAlive(true)
75 | }
76 | if command == CmdUDPAssociate {
77 | defer conn.Close()
78 | io.Copy(ioutil.Discard, conn)
79 | return
80 | }
81 | cb(conn, target)
82 | }
83 |
--------------------------------------------------------------------------------
/components/utils/pool/alloc.go:
--------------------------------------------------------------------------------
1 | package pool
2 |
3 | // Steal from https://github.com/Dreamacro/clash/common/pool
4 |
5 | import (
6 | "errors"
7 | "math/bits"
8 | "sync"
9 | )
10 |
11 | var defaultAllocator *Allocator
12 |
13 | func init() {
14 | defaultAllocator = NewAllocator()
15 | }
16 |
17 | // Allocator for incoming frames, optimized to prevent overwriting after zeroing
18 | type Allocator struct {
19 | buffers []sync.Pool
20 | }
21 |
22 | // NewAllocator initiates a []byte allocator for frames less than 65536 bytes,
23 | // the waste(memory fragmentation) of space allocation is guaranteed to be
24 | // no more than 50%.
25 | func NewAllocator() *Allocator {
26 | alloc := new(Allocator)
27 | alloc.buffers = make([]sync.Pool, 17) // 1B -> 64K
28 | for k := range alloc.buffers {
29 | i := k
30 | alloc.buffers[k].New = func() interface{} {
31 | return make([]byte, 1< 65536 {
40 | return nil
41 | }
42 |
43 | bits := msb(size)
44 | if size == 1< 65536 || cap(buf) != 1<.
13 | */
14 |
15 | package utils
16 |
17 | import (
18 | "io"
19 | "net"
20 | "time"
21 |
22 | p "github.com/icpz/open-snell/components/utils/pool"
23 | )
24 |
25 | func Relay(left, right net.Conn) (el, er error) {
26 | ch := make(chan error)
27 |
28 | go func() {
29 | buf := p.Get(p.RelayBufferSize)
30 | _, err := io.CopyBuffer(left, right, buf)
31 | p.Put(buf)
32 | left.SetReadDeadline(time.Now())
33 | ch <- err
34 | }()
35 |
36 | buf := p.Get(p.RelayBufferSize)
37 | _, el = io.CopyBuffer(right, left, buf)
38 | p.Put(buf)
39 | right.SetReadDeadline(time.Now())
40 | er = <-ch
41 |
42 | if err, ok := el.(net.Error); ok && err.Timeout() {
43 | el = nil
44 | }
45 | if err, ok := er.(net.Error); ok && err.Timeout() {
46 | er = nil
47 | }
48 |
49 | return
50 | }
51 |
--------------------------------------------------------------------------------
/constants/version.go:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of open-snell.
3 | * open-snell is free software: you can redistribute it and/or modify
4 | * it under the terms of the GNU General Public License as published by
5 | * the Free Software Foundation, either version 3 of the License, or
6 | * (at your option) any later version.
7 | * open-snell is distributed in the hope that it will be useful,
8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 | * GNU General Public License for more details.
11 | * You should have received a copy of the GNU General Public License
12 | * along with open-snell. If not, see .
13 | */
14 |
15 | package constants
16 |
17 | var Version string = "nightly"
18 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/icpz/open-snell
2 |
3 | go 1.17
4 |
5 | require (
6 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
7 | github.com/hashicorp/golang-lru v0.5.4
8 | github.com/icpz/pool v0.0.0-20200716103602-44a34f9008c6
9 | golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9
10 | gopkg.in/ini.v1 v1.57.0
11 | )
12 |
13 | require (
14 | github.com/smartystreets/goconvey v1.6.4 // indirect
15 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd // indirect
16 | )
17 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
2 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
3 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
4 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
5 | github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
6 | github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
7 | github.com/icpz/pool v0.0.0-20200716103602-44a34f9008c6 h1:R2BPbdcGYy6jZpyMx3PkdFcX/eiX/6QxHb6TDgXH5kA=
8 | github.com/icpz/pool v0.0.0-20200716103602-44a34f9008c6/go.mod h1:4IbCleUMuSl5DlXd3zx2r/yRjxfSz5UnQHhSQgjmsKg=
9 | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
10 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
11 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
12 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
13 | github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
14 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
15 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
16 | golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM=
17 | golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
18 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
19 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
20 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
21 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
22 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
23 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
24 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
25 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
26 | gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww=
27 | gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
28 |
--------------------------------------------------------------------------------