├── .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 | --------------------------------------------------------------------------------