├── .github ├── dependabot.yml └── workflows │ └── build.yml ├── .gitignore ├── .goreleaser.yaml ├── LICENSE ├── README.md ├── arpr └── arp.go ├── cfg └── config.go ├── common └── pool │ ├── alloc.go │ ├── alloc_test.go │ ├── buffer.go │ └── pool.go ├── config.json ├── config.json.fullsample ├── core ├── adapter │ ├── adapter.go │ └── handler.go ├── device │ ├── device.go │ ├── iobased │ │ └── endpoint.go │ └── pcap.go ├── nic.go ├── option │ └── option.go ├── route.go ├── stack.go ├── tcp.go ├── tunnel.go └── udpforwarder.go ├── default.pgo ├── dialer ├── dialer.go ├── sockopt.go ├── sockopt_darwin.go ├── sockopt_freebsd.go ├── sockopt_linux.go ├── sockopt_openbsd.go ├── sockopt_others.go └── sockopt_windows.go ├── go.mod ├── go.sum ├── main.go ├── md ├── metadata.go └── network.go ├── proxy ├── base.go ├── direct.go ├── mode.go ├── proxy.go ├── reject.go ├── router.go ├── socks5.go └── util.go ├── transport ├── socks5.go └── socks5_test.go └── tunnel ├── addr.go ├── tcp.go ├── tunnel.go └── udp.go /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | commit-message: 8 | prefix: "chore" 9 | include: "scope" 10 | 11 | - package-ecosystem: "gomod" 12 | directory: "/" 13 | schedule: 14 | interval: "daily" 15 | commit-message: 16 | prefix: "chore" 17 | include: "scope" -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | pull_request: 5 | push: 6 | # run only against tags 7 | tags: 8 | - "*" 9 | 10 | permissions: 11 | contents: write 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | with: 19 | fetch-depth: 0 20 | - uses: actions/setup-go@v5 21 | with: 22 | go-version: stable 23 | - run: sudo apt-get install -y libpcap-dev 24 | - run: go mod tidy 25 | - run: go test -v ./... 26 | - uses: goreleaser/goreleaser-action@v6 27 | with: 28 | distribution: goreleaser 29 | version: latest 30 | args: release --clean 31 | env: 32 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | 23 | *.DS_Store 24 | .idea 25 | .vscode 26 | /build_assets 27 | *.zip 28 | *.tar.gz -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | project_name: go-pcap2socks 4 | builds: 5 | - env: 6 | - CGO_ENABLED=1 7 | goos: 8 | - windows 9 | - linux 10 | goarch: 11 | - amd64 12 | nfpms: 13 | - maintainer: Daniil Sokolyuk 14 | description: Sample project. 15 | homepage: https://github.com/DaniilSokolyuk/go-pcap2socks 16 | license: GPL-3.0 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 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 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant 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 install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | 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 updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 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 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-pcap2socks 2 | go-pcap2socks is a proxy that redirects traffic from any device to a SOCKS5 proxy. 3 | 4 | go-pcap2socks functions like a router, allowing you to connect various devices such as an **XBOX**, **PlayStation (PS4, PS5)**, **Nintendo Switch**, mobile phones, printers and others to any SOCKS5 proxy server. Additionally, you can host a SOCKS5 proxy server on the same PC to use services like a VPN or a game booster/accelerator for reduced latency, you can also share a working VPN from your computer to your mobile phone. 5 | 6 | ## Dependencies 7 | For **Windows**, install [Npcap](http://www.npcap.org/) or WinPcap. If you choose Npcap, ensure to install it in "WinPcap API-compatible Mode". For **macOS**, **Linux**, and other operating systems, use libpcap. 8 | 9 | ## Config 10 | Config example is [here](https://github.com/DaniilSokolyuk/go-pcap2socks/blob/main/config.json) 11 | 12 | ## Credits 13 | - https://github.com/google/gvisor - TCP/IP stack 14 | - https://github.com/zhxie/pcap2socks - Idea 15 | - https://github.com/xjasonlyu/tun2socks - socks5 client 16 | - https://github.com/SagerNet/sing-box Full Cone NAT 17 | -------------------------------------------------------------------------------- /arpr/arp.go: -------------------------------------------------------------------------------- 1 | package arpr 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | 7 | "github.com/gopacket/gopacket" 8 | "github.com/gopacket/gopacket/layers" 9 | ) 10 | 11 | func SendGratuitousArp(localIP net.IP, localMAC net.HardwareAddr) ([]byte, error) { 12 | ethernet := &layers.Ethernet{ 13 | SrcMAC: localMAC, 14 | DstMAC: layers.EthernetBroadcast, 15 | EthernetType: layers.EthernetTypeIPv4, 16 | } 17 | arp := &layers.ARP{ 18 | AddrType: layers.LinkTypeEthernet, 19 | Protocol: layers.EthernetTypeIPv4, 20 | HwAddressSize: 6, 21 | ProtAddressSize: 4, 22 | Operation: layers.ARPRequest, 23 | SourceHwAddress: localMAC, 24 | SourceProtAddress: localIP, 25 | DstHwAddress: net.HardwareAddr{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 26 | DstProtAddress: localIP, 27 | } 28 | 29 | sbuf := gopacket.NewSerializeBuffer() 30 | options := gopacket.SerializeOptions{ 31 | ComputeChecksums: true, 32 | FixLengths: true, 33 | } 34 | 35 | if err := gopacket.SerializeLayers(sbuf, options, ethernet, arp); err != nil { 36 | return nil, err 37 | } 38 | 39 | return sbuf.Bytes(), nil 40 | } 41 | 42 | func SendReply(arp *layers.ARP, localIP net.IP, localMAC net.HardwareAddr) ([]byte, error) { 43 | if arp.Operation != layers.ARPRequest { 44 | return nil, fmt.Errorf("not an ARP request") 45 | } 46 | 47 | ethernetResp := &layers.Ethernet{ 48 | SrcMAC: localMAC, 49 | DstMAC: arp.SourceHwAddress, 50 | EthernetType: layers.EthernetTypeARP, 51 | } 52 | arpResp := &layers.ARP{ 53 | AddrType: arp.AddrType, 54 | Protocol: arp.Protocol, 55 | HwAddressSize: arp.HwAddressSize, 56 | ProtAddressSize: arp.ProtAddressSize, 57 | Operation: layers.ARPReply, 58 | SourceHwAddress: localMAC, 59 | SourceProtAddress: localIP, 60 | DstHwAddress: arp.SourceHwAddress, 61 | DstProtAddress: arp.SourceProtAddress, 62 | } 63 | 64 | sbuf := gopacket.NewSerializeBuffer() 65 | options := gopacket.SerializeOptions{ 66 | ComputeChecksums: true, 67 | FixLengths: true, 68 | } 69 | 70 | if err := gopacket.SerializeLayers(sbuf, options, ethernetResp, arpResp); err != nil { 71 | panic(err) 72 | } 73 | 74 | // If we failed to write the message, we do so silently. Packet loss happen... 75 | return sbuf.Bytes(), nil 76 | } 77 | -------------------------------------------------------------------------------- /cfg/config.go: -------------------------------------------------------------------------------- 1 | package cfg 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net" 7 | "os" 8 | "strconv" 9 | "strings" 10 | ) 11 | 12 | func Exists(filePath string) bool { 13 | if _, err := os.Stat(filePath); os.IsNotExist(err) { 14 | return false 15 | } 16 | 17 | return true 18 | } 19 | 20 | func Load(filePath string) (*Config, error) { 21 | file, err := os.Open(filePath) 22 | if err != nil { 23 | return nil, err 24 | } 25 | defer file.Close() 26 | 27 | config := &Config{} 28 | decoder := json.NewDecoder(file) 29 | err = decoder.Decode(config) 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | config.Normalize() 35 | 36 | return config, nil 37 | } 38 | 39 | type Config struct { 40 | ExecuteOnStart []string `json:"executeOnStart"` 41 | PCAP PCAP `json:"pcap"` 42 | Routing struct { 43 | Rules []Rule `json:"rules"` 44 | } `json:"routing"` 45 | Outbounds []Outbound `json:"outbounds"` 46 | } 47 | 48 | type PCAP struct { 49 | InterfaceGateway string `json:"interfaceGateway"` 50 | MTU uint32 `json:"mtu"` 51 | Network string `json:"network"` 52 | LocalIP string `json:"localIP"` 53 | LocalMAC string `json:"localMAC"` 54 | } 55 | 56 | func (c *Config) Normalize() { 57 | for i := range c.Routing.Rules { 58 | c.Routing.Rules[i].Normalize() 59 | } 60 | } 61 | 62 | type Rule struct { 63 | SrcPort string `json:"srcPort,omitempty"` 64 | DstPort string `json:"dstPort,omitempty"` 65 | SrcIP []string `json:"srcIP,omitempty"` 66 | DstIP []string `json:"dstIP,omitempty"` 67 | OutboundTag string `json:"outboundTag"` 68 | 69 | SrcPorts map[uint16]struct{} 70 | DstPorts map[uint16]struct{} 71 | SrcIPs []net.IPNet 72 | DstIPs []net.IPNet 73 | } 74 | 75 | func (r *Rule) Normalize() { 76 | r.SrcPorts = mustPorts(r.SrcPort) 77 | r.DstPorts = mustPorts(r.DstPort) 78 | 79 | r.SrcIPs = mustToNetIP(r.SrcIP) 80 | r.DstIPs = mustToNetIP(r.DstIP) 81 | } 82 | 83 | type Outbound struct { 84 | Direct *OutboundDirect `json:"direct,omitempty"` 85 | Socks *OutboundSocks `json:"socks,omitempty"` 86 | Reject *OutboundReject `json:"reject,omitempty"` 87 | Tag string `json:"tag,omitempty"` 88 | } 89 | 90 | type OutboundDirect struct{} 91 | type OutboundReject struct{} 92 | 93 | type OutboundSocks struct { 94 | Address string `json:"address"` 95 | Username string `json:"username"` 96 | Password string `json:"password"` 97 | } 98 | 99 | func mustToNetIP(addrs []string) []net.IPNet { 100 | ips := make([]net.IPNet, 0, len(addrs)) 101 | 102 | if len(addrs) == 0 { 103 | return ips 104 | } 105 | 106 | for _, addr := range addrs { 107 | if !strings.Contains(addr, "/") { 108 | addr += "/32" 109 | } 110 | 111 | _, ipNet, err := net.ParseCIDR(addr) 112 | if err != nil { 113 | panic(fmt.Sprintf("invalid ip: %s", addr)) 114 | } 115 | 116 | ips = append(ips, *ipNet) 117 | } 118 | 119 | return ips 120 | } 121 | 122 | func mustPorts(ports string) map[uint16]struct{} { 123 | m := make(map[uint16]struct{}) 124 | 125 | if ports == "" { 126 | return m 127 | } 128 | 129 | for _, port := range strings.Split(ports, ",") { 130 | if strings.Contains(port, "-") { 131 | p := strings.Split(strings.TrimSpace(port), "-") 132 | if len(p) != 2 { 133 | panic(fmt.Sprintf("invalid port: %s", port)) 134 | } 135 | 136 | mmin, err := strconv.ParseUint(strings.TrimSpace(p[0]), 10, 16) 137 | if err != nil { 138 | panic(fmt.Sprintf("invalid port: %s", p[0])) 139 | } 140 | 141 | mmax, err := strconv.ParseUint(strings.TrimSpace(p[1]), 10, 16) 142 | if err != nil { 143 | panic(fmt.Sprintf("invalid port: %s", p[1])) 144 | } 145 | 146 | if mmin > mmax { 147 | panic(fmt.Sprintf("invalid port: %s", port)) 148 | } 149 | 150 | for i := mmin; i <= mmax; i++ { 151 | m[uint16(i)] = struct{}{} 152 | } 153 | 154 | continue 155 | } 156 | 157 | mustPort, err := strconv.ParseUint(strings.TrimSpace(port), 10, 16) 158 | if err != nil { 159 | panic(fmt.Sprintf("invalid port: %s", port)) 160 | } 161 | 162 | m[uint16(mustPort)] = struct{}{} 163 | } 164 | 165 | return m 166 | } 167 | -------------------------------------------------------------------------------- /common/pool/alloc.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | "errors" 5 | "math/bits" 6 | "sync" 7 | ) 8 | 9 | var _allocator = NewAllocator() 10 | 11 | // Allocator for incoming frames, optimized to prevent overwriting 12 | // after zeroing. 13 | type Allocator struct { 14 | buffers []sync.Pool 15 | } 16 | 17 | // NewAllocator initiates a []byte allocator for frames less than 18 | // 65536 bytes, the waste(memory fragmentation) of space allocation 19 | // is guaranteed to be no more than 50%. 20 | func NewAllocator() *Allocator { 21 | alloc := &Allocator{} 22 | alloc.buffers = make([]sync.Pool, 17) // 1B -> 64K 23 | for k := range alloc.buffers { 24 | i := k 25 | alloc.buffers[k].New = func() any { 26 | return make([]byte, 1< 65536 { 35 | return nil 36 | } 37 | 38 | b := msb(size) 39 | if size == 1< 65536 || cap(buf) != 1< mtu { 106 | continue 107 | } 108 | 109 | if !e.IsAttached() { 110 | continue /* unattached, drop packet */ 111 | } 112 | 113 | pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{ 114 | Payload: buffer.MakeWithData(data), 115 | IsForwardedPacket: true, 116 | }) 117 | 118 | e.InjectInbound(header.EthernetProtocolAll, pkt) 119 | 120 | pkt.DecRef() 121 | } 122 | } 123 | 124 | // outboundLoop reads outbound packets from channel, and then it calls 125 | // writePacket to send those packets back to lower layer. 126 | func (e *Endpoint) outboundLoop(ctx context.Context) { 127 | for { 128 | pkt := e.ReadContext(ctx) 129 | if pkt == nil { 130 | break 131 | } 132 | e.writePacket(pkt) 133 | } 134 | } 135 | 136 | // writePacket writes outbound packets to the io.Writer. 137 | func (e *Endpoint) writePacket(pkt *stack.PacketBuffer) tcpip.Error { 138 | defer pkt.DecRef() 139 | 140 | view := pkt.ToView() 141 | defer view.Release() 142 | _, err := view.WriteTo(e.rw) 143 | if err != nil { 144 | return &tcpip.ErrInvalidEndpointState{} 145 | } 146 | return nil 147 | } 148 | 149 | type ReadWriter interface { 150 | io.Writer 151 | Read() []byte 152 | } 153 | -------------------------------------------------------------------------------- /core/device/pcap.go: -------------------------------------------------------------------------------- 1 | package device 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "log/slog" 7 | "net" 8 | "sync" 9 | 10 | "github.com/DaniilSokolyuk/go-pcap2socks/arpr" 11 | "github.com/DaniilSokolyuk/go-pcap2socks/cfg" 12 | "github.com/DaniilSokolyuk/go-pcap2socks/core" 13 | "github.com/DaniilSokolyuk/go-pcap2socks/core/device/iobased" 14 | "github.com/gopacket/gopacket" 15 | "github.com/gopacket/gopacket/layers" 16 | "github.com/gopacket/gopacket/pcap" 17 | "github.com/jackpal/gateway" 18 | "gvisor.dev/gvisor/pkg/tcpip" 19 | "gvisor.dev/gvisor/pkg/tcpip/header" 20 | "gvisor.dev/gvisor/pkg/tcpip/link/ethernet" 21 | ) 22 | 23 | type PCAP struct { 24 | *ethernet.Endpoint 25 | 26 | name string 27 | ep *iobased.Endpoint 28 | 29 | network *net.IPNet 30 | localIP net.IP 31 | localMAC net.HardwareAddr 32 | handle *pcap.Handle 33 | ipMacTable map[string]net.HardwareAddr 34 | Interface net.Interface 35 | rMux sync.Mutex 36 | stacker func() Stacker 37 | } 38 | 39 | const offset = 0 40 | 41 | func Open(cfg cfg.PCAP, stacker func() Stacker) (_ Device, err error) { 42 | defer func() { 43 | if r := recover(); r != nil { 44 | err = fmt.Errorf("open tun: %v", r) 45 | } 46 | }() 47 | 48 | ifce, dev := findDevInterface(cfg.InterfaceGateway) 49 | slog.Info("Using ethernet interface", "interface", ifce.Name, "name", dev.Name, "mac", ifce.HardwareAddr.String()) 50 | 51 | _, network, err := net.ParseCIDR(cfg.Network) 52 | if err != nil { 53 | return nil, fmt.Errorf("parse cidr error: %w", err) 54 | } 55 | 56 | localIP := net.ParseIP(cfg.LocalIP) 57 | if localIP == nil { 58 | return nil, fmt.Errorf("parse local ip error: %w", err) 59 | } 60 | 61 | localIP = localIP.To4() 62 | if !network.Contains(localIP) { 63 | return nil, fmt.Errorf("local ip (%s) not in network (%s)", localIP, network) 64 | } 65 | 66 | var localMAC net.HardwareAddr 67 | if cfg.LocalMAC != "" { 68 | localMAC, err = net.ParseMAC(cfg.LocalMAC) 69 | if localMAC == nil { 70 | return nil, fmt.Errorf("parse local mac error: %w", err) 71 | } 72 | } else { 73 | localMAC = ifce.HardwareAddr 74 | } 75 | 76 | slog.Default().Info("Enter this settings in your device's network settings", 77 | "ip", network.String(), 78 | "mask", net.IP(network.Mask).String(), 79 | "gateway", localIP.String()) 80 | 81 | pcaphInactive, err := createPcapHandle(dev) 82 | if err != nil { 83 | return nil, err 84 | } 85 | 86 | pcaph, err := pcaphInactive.Activate() 87 | if err != nil { 88 | return nil, fmt.Errorf("open live error: %w", err) 89 | } 90 | 91 | //arp or src net 172.24.2.0/24 92 | err = pcaph.SetBPFFilter(fmt.Sprintf("arp or src net %s", network.String())) 93 | if err != nil { 94 | return nil, fmt.Errorf("set bpf filter error: %w", err) 95 | } 96 | 97 | mtu := cfg.MTU 98 | if mtu == 0 { 99 | mtu = uint32(ifce.MTU) 100 | } 101 | 102 | t := &PCAP{ 103 | name: "dspcap", 104 | stacker: stacker, 105 | Interface: ifce, 106 | network: network, 107 | localIP: localIP, 108 | localMAC: localMAC, 109 | handle: pcaph, 110 | ipMacTable: make(map[string]net.HardwareAddr), 111 | } 112 | 113 | ep, err := iobased.New(t, mtu, offset, t.localMAC) 114 | if err != nil { 115 | return nil, fmt.Errorf("create endpoint: %w", err) 116 | } 117 | t.ep = ep 118 | // we are in L2 and using ethernet header 119 | t.Endpoint = ethernet.New(ep) 120 | 121 | // send gratuitous arp 122 | { 123 | arpGratuitous, err := arpr.SendGratuitousArp(localIP, localMAC) 124 | if err != nil { 125 | return nil, fmt.Errorf("send gratuitous arp error: %w", err) 126 | } 127 | 128 | err = t.handle.WritePacketData(arpGratuitous) 129 | if err != nil { 130 | return nil, fmt.Errorf("write packet error: %w", err) 131 | 132 | } 133 | } 134 | 135 | return t, nil 136 | } 137 | 138 | func createPcapHandle(dev pcap.Interface) (*pcap.InactiveHandle, error) { 139 | handle, err := pcap.NewInactiveHandle(dev.Name) 140 | if err != nil { 141 | return nil, fmt.Errorf("new inactive handle error: %w", err) 142 | } 143 | 144 | err = handle.SetPromisc(true) 145 | if err != nil { 146 | return nil, fmt.Errorf("set promisc error: %w", err) 147 | } 148 | 149 | err = handle.SetSnapLen(1600) 150 | if err != nil { 151 | return nil, fmt.Errorf("set snap len error: %w", err) 152 | } 153 | 154 | err = handle.SetTimeout(pcap.BlockForever) 155 | if err != nil { 156 | return nil, fmt.Errorf("set timeout error: %w", err) 157 | } 158 | 159 | err = handle.SetImmediateMode(true) 160 | if err != nil { 161 | return nil, fmt.Errorf("set immediate mode error: %w", err) 162 | } 163 | 164 | err = handle.SetBufferSize(512 * 1024) 165 | if err != nil { 166 | return nil, fmt.Errorf("set buffer size error: %w", err) 167 | } 168 | 169 | return handle, nil 170 | } 171 | 172 | func (t *PCAP) Read() []byte { 173 | t.rMux.Lock() 174 | defer t.rMux.Unlock() 175 | data, _, err := t.handle.ZeroCopyReadPacketData() 176 | if err != nil { 177 | slog.Error("read packet error: %w", err) 178 | return nil 179 | } 180 | 181 | ethProtocol := header.Ethernet(data) 182 | switch ethProtocol.Type() { 183 | case header.IPv4ProtocolNumber: 184 | ipProtocol := header.IPv4(data[14:]) 185 | srcAddress := ipProtocol.SourceAddress() 186 | if !t.network.Contains(srcAddress.AsSlice()) { 187 | return nil 188 | } 189 | if bytes.Compare(srcAddress.AsSlice(), t.localIP) != 0 { 190 | t.SetHardwareAddr(srcAddress.AsSlice(), []byte(ethProtocol.SourceAddress())) 191 | } 192 | case header.ARPProtocolNumber: 193 | gPckt := gopacket.NewPacket(data, layers.LayerTypeEthernet, gopacket.Default) 194 | arpLayer, isArp := gPckt.Layer(layers.LayerTypeARP).(*layers.ARP) 195 | if !isArp { 196 | return nil 197 | } 198 | 199 | srcIP := net.IP(arpLayer.SourceProtAddress) 200 | dstIP := net.IP(arpLayer.DstProtAddress) 201 | // //gvisor handle arp requests, but we should filter out arp requests from the expected network 202 | // cant use gvisor check due spoofing 203 | if bytes.Compare(srcIP, t.localIP) != 0 && 204 | bytes.Compare(dstIP, t.localIP) == 0 && 205 | t.network.Contains(srcIP) { 206 | t.SetHardwareAddr(srcIP, arpLayer.SourceHwAddress) 207 | } else { 208 | return nil 209 | } 210 | 211 | default: 212 | return nil 213 | } 214 | 215 | return data 216 | } 217 | 218 | func (t *PCAP) Write(p []byte) (n int, err error) { 219 | err = t.handle.WritePacketData(p) 220 | if err != nil { 221 | slog.Error("write packet error: %w", err) 222 | return 0, nil 223 | } 224 | 225 | //fmt.Println("==============================reply: " + gopacket.NewPacket(p, layers.LayerTypeEthernet, gopacket.Default).String()) 226 | return len(p), nil 227 | } 228 | 229 | func (t *PCAP) Name() string { 230 | return t.name 231 | } 232 | 233 | func (t *PCAP) Close() { 234 | defer t.ep.Close() 235 | t.handle.Close() 236 | } 237 | 238 | func (t *PCAP) Type() string { 239 | return "pcap" 240 | } 241 | 242 | func (t *PCAP) SetHardwareAddr(srcIP net.IP, srcMAC net.HardwareAddr) { 243 | if _, ok := t.ipMacTable[string(srcIP)]; !ok { 244 | slog.Info(fmt.Sprintf("Device %s (%s) joined the network", srcIP, srcMAC)) 245 | t.ipMacTable[string(srcIP)] = srcMAC 246 | // after restart app some devices doesnt react to GratuitousArp, so we need to add them manually 247 | t.stacker().AddStaticNeighbor(core.NicID, header.IPv4ProtocolNumber, tcpip.AddrFrom4Slice(srcIP), tcpip.LinkAddress(srcMAC)) 248 | } 249 | } 250 | 251 | func findDevInterface(cfgIfce string) (net.Interface, pcap.Interface) { 252 | var targetIface net.IP 253 | if cfgIfce != "" { 254 | targetIface = net.ParseIP(cfgIfce) 255 | if targetIface == nil { 256 | panic(fmt.Errorf("parse ip error, %s", cfgIfce)) 257 | } 258 | } else { 259 | var err error 260 | targetIface, err = gateway.DiscoverInterface() 261 | if err != nil { 262 | panic(fmt.Errorf("discover interface error: %w", err)) 263 | } 264 | } 265 | 266 | // Get a list of all interfaces. 267 | ifaces, err := net.Interfaces() 268 | if err != nil { 269 | panic(err) 270 | } 271 | 272 | // Get a list of all devices 273 | devices, err := pcap.FindAllDevs() 274 | if err != nil { 275 | panic(err) 276 | } 277 | 278 | var foundIface net.Interface 279 | var foundDev pcap.Interface 280 | 281 | for _, iface := range ifaces { 282 | var addr *net.IPNet 283 | 284 | if addrs, err := iface.Addrs(); err != nil { 285 | continue 286 | } else { 287 | for _, a := range addrs { 288 | if ipnet, ok := a.(*net.IPNet); ok { 289 | if ip4 := ipnet.IP.To4(); ip4 != nil && bytes.Equal(ip4, targetIface.To4()) { 290 | addr = &net.IPNet{ 291 | IP: ip4, 292 | Mask: ipnet.Mask[len(ipnet.Mask)-4:], 293 | } 294 | break 295 | } 296 | } 297 | } 298 | } 299 | 300 | if addr == nil { 301 | continue 302 | } 303 | 304 | for _, dev := range devices { 305 | for _, address := range dev.Addresses { 306 | if address.IP.Equal(addr.IP) { 307 | foundIface = iface 308 | foundDev = dev 309 | break 310 | } 311 | } 312 | } 313 | } 314 | 315 | return foundIface, foundDev 316 | } 317 | 318 | type Stacker interface { 319 | AddStaticNeighbor(nicID tcpip.NICID, protocol tcpip.NetworkProtocolNumber, addr tcpip.Address, linkAddr tcpip.LinkAddress) tcpip.Error 320 | } 321 | -------------------------------------------------------------------------------- /core/nic.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "fmt" 5 | "github.com/DaniilSokolyuk/go-pcap2socks/core/option" 6 | "net" 7 | 8 | "gvisor.dev/gvisor/pkg/tcpip" 9 | "gvisor.dev/gvisor/pkg/tcpip/network/ipv4" 10 | "gvisor.dev/gvisor/pkg/tcpip/network/ipv6" 11 | "gvisor.dev/gvisor/pkg/tcpip/stack" 12 | ) 13 | 14 | const ( 15 | // nicPromiscuousModeEnabled is the value used by stack to enable 16 | // or disable NIC's promiscuous mode. 17 | nicPromiscuousModeEnabled = true 18 | 19 | // nicSpoofingEnabled is the value used by stack to enable or disable 20 | // NIC's spoofing. 21 | nicSpoofingEnabled = true 22 | ) 23 | 24 | // withCreatingNIC creates NIC for stack. 25 | func withCreatingNIC(nicID tcpip.NICID, ep stack.LinkEndpoint) option.Option { 26 | return func(s *stack.Stack) error { 27 | if err := s.CreateNICWithOptions(nicID, ep, 28 | stack.NICOptions{ 29 | Disabled: false, 30 | // If no queueing discipline was specified 31 | // provide a stub implementation that just 32 | // delegates to the lower link endpoint. 33 | QDisc: nil, 34 | }); err != nil { 35 | return fmt.Errorf("create NIC: %s", err) 36 | } 37 | return nil 38 | } 39 | } 40 | 41 | // withPromiscuousMode sets promiscuous mode in the given NICs. 42 | func withPromiscuousMode(nicID tcpip.NICID, v bool) option.Option { 43 | return func(s *stack.Stack) error { 44 | if err := s.SetPromiscuousMode(nicID, v); err != nil { 45 | return fmt.Errorf("set promiscuous mode: %s", err) 46 | } 47 | return nil 48 | } 49 | } 50 | 51 | // withSpoofing sets address spoofing in the given NICs, allowing 52 | // endpoints to bind to any address in the NIC. 53 | func withSpoofing(nicID tcpip.NICID, v bool) option.Option { 54 | return func(s *stack.Stack) error { 55 | if err := s.SetSpoofing(nicID, v); err != nil { 56 | return fmt.Errorf("set spoofing: %s", err) 57 | } 58 | return nil 59 | } 60 | } 61 | 62 | // withMulticastGroups adds a NIC to the given multicast groups. 63 | func withMulticastGroups(nicID tcpip.NICID, multicastGroups []net.IP) option.Option { 64 | return func(s *stack.Stack) error { 65 | if len(multicastGroups) == 0 { 66 | return nil 67 | } 68 | // The default NIC of tun2socks is working on Spoofing mode. When the UDP Endpoint 69 | // tries to use a non-local address to connect, the network stack will 70 | // generate a temporary addressState to build the route, which can be primary 71 | // but is ephemeral. Nevertheless, when the UDP Endpoint tries to use a 72 | // multicast address to connect, the network stack will select an available 73 | // primary addressState to build the route. However, when tun2socks is in the 74 | // just-initialized or idle state, there will be no available primary addressState, 75 | // and the connect operation will fail. Therefore, we need to add permanent addresses, 76 | // e.g. 10.0.0.1/8 and fd00:1/8, to the default NIC, which are only used to build 77 | // routes for multicast response and do not affect other connections. 78 | // 79 | // In fact, for multicast, the sender normally does not expect a response. 80 | // So, the ep.net.Connect is unnecessary. If we implement a custom UDP Forwarder 81 | // and ForwarderRequest in the future, we can remove these code. 82 | s.AddProtocolAddress( 83 | nicID, 84 | tcpip.ProtocolAddress{ 85 | Protocol: ipv4.ProtocolNumber, 86 | AddressWithPrefix: tcpip.AddressWithPrefix{ 87 | Address: tcpip.AddrFrom4([4]byte{0x0a, 0, 0, 0x01}), 88 | PrefixLen: 8, 89 | }, 90 | }, 91 | stack.AddressProperties{PEB: stack.CanBePrimaryEndpoint}, 92 | ) 93 | s.AddProtocolAddress( 94 | nicID, 95 | tcpip.ProtocolAddress{ 96 | Protocol: ipv6.ProtocolNumber, 97 | AddressWithPrefix: tcpip.AddressWithPrefix{ 98 | Address: tcpip.AddrFrom16([16]byte{0xfd, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01}), 99 | PrefixLen: 8, 100 | }, 101 | }, 102 | stack.AddressProperties{PEB: stack.CanBePrimaryEndpoint}, 103 | ) 104 | for _, multicastGroup := range multicastGroups { 105 | if ip := multicastGroup.To4(); ip != nil { 106 | if err := s.JoinGroup(ipv4.ProtocolNumber, nicID, tcpip.AddrFrom4Slice(ip)); err != nil { 107 | return fmt.Errorf("join multicast group: %s", err) 108 | } 109 | } else { 110 | ip := multicastGroup.To16() 111 | if err := s.JoinGroup(ipv6.ProtocolNumber, nicID, tcpip.AddrFrom16Slice(ip)); err != nil { 112 | return fmt.Errorf("join multicast group: %s", err) 113 | } 114 | } 115 | } 116 | return nil 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /core/option/option.go: -------------------------------------------------------------------------------- 1 | package option 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | 7 | "golang.org/x/time/rate" 8 | "gvisor.dev/gvisor/pkg/tcpip" 9 | "gvisor.dev/gvisor/pkg/tcpip/network/ipv4" 10 | "gvisor.dev/gvisor/pkg/tcpip/network/ipv6" 11 | "gvisor.dev/gvisor/pkg/tcpip/stack" 12 | "gvisor.dev/gvisor/pkg/tcpip/transport/tcp" 13 | ) 14 | 15 | const ( 16 | // defaultTimeToLive specifies the default TTL used by stack. 17 | defaultTimeToLive uint8 = 64 18 | 19 | // ipForwardingEnabled is the value used by stack to enable packet 20 | // forwarding between NICs. 21 | ipForwardingEnabled = true 22 | 23 | // icmpBurst is the default number of ICMP messages that can be sent in 24 | // a single burst. 25 | icmpBurst = 50 26 | 27 | // icmpLimit is the default maximum number of ICMP messages permitted 28 | // by this rate limiter. 29 | icmpLimit rate.Limit = 1000 30 | 31 | // tcpCongestionControl is the congestion control algorithm used by 32 | // stack. ccReno is the default option in gVisor stack. 33 | tcpCongestionControlAlgorithm = "reno" // "reno" or "cubic" 34 | 35 | // tcpDelayEnabled is the value used by stack to enable or disable 36 | // tcp delay option. Disable Nagle's algorithm here by default. 37 | tcpDelayEnabled = false 38 | 39 | // tcpModerateReceiveBufferEnabled is the value used by stack to 40 | // enable or disable tcp receive buffer auto-tuning option. 41 | tcpModerateReceiveBufferEnabled = false 42 | 43 | // tcpSACKEnabled is the value used by stack to enable or disable 44 | // tcp selective ACK. 45 | tcpSACKEnabled = true 46 | 47 | // tcpRecovery is the loss detection algorithm used by TCP. 48 | tcpRecovery = tcpip.TCPRACKLossDetection 49 | 50 | // tcpMinBufferSize is the smallest size of a send/recv buffer. 51 | tcpMinBufferSize = tcp.MinBufferSize 52 | 53 | // tcpMaxBufferSize is the maximum permitted size of a send/recv buffer. 54 | tcpMaxBufferSize = tcp.MaxBufferSize 55 | 56 | // tcpDefaultBufferSize is the default size of the send buffer for 57 | // a transport endpoint. 58 | tcpDefaultSendBufferSize = tcp.DefaultSendBufferSize 59 | 60 | // tcpDefaultReceiveBufferSize is the default size of the receive buffer 61 | // for a transport endpoint. 62 | tcpDefaultReceiveBufferSize = tcp.DefaultReceiveBufferSize 63 | ) 64 | 65 | type Option func(*stack.Stack) error 66 | 67 | // WithDefault sets all default values for stack. 68 | func WithDefault() Option { 69 | return func(s *stack.Stack) error { 70 | opts := []Option{ 71 | WithDefaultTTL(defaultTimeToLive), 72 | WithForwarding(ipForwardingEnabled), 73 | 74 | // Config default stack ICMP settings. 75 | WithICMPBurst(icmpBurst), WithICMPLimit(icmpLimit), 76 | 77 | // We expect no packet loss, therefore we can bump buffers. 78 | // Too large buffers thrash cache, so there is little point 79 | // in too large buffers. 80 | // 81 | // Ref: https://github.com/cloudflare/slirpnetstack/blob/master/stack.go 82 | WithTCPSendBufferSizeRange(tcpMinBufferSize, tcpDefaultSendBufferSize, tcpMaxBufferSize), 83 | WithTCPReceiveBufferSizeRange(tcpMinBufferSize, tcpDefaultReceiveBufferSize, tcpMaxBufferSize), 84 | 85 | WithTCPCongestionControl(tcpCongestionControlAlgorithm), 86 | WithTCPDelay(tcpDelayEnabled), 87 | 88 | // Receive Buffer Auto-Tuning Option, see: 89 | // https://github.com/google/gvisor/issues/1666 90 | WithTCPModerateReceiveBuffer(tcpModerateReceiveBufferEnabled), 91 | 92 | // TCP selective ACK Option, see: 93 | // https://tools.ietf.org/html/rfc2018 94 | WithTCPSACKEnabled(tcpSACKEnabled), 95 | 96 | // TCPRACKLossDetection: indicates RACK is used for loss detection and 97 | // recovery. 98 | // 99 | // TCPRACKStaticReoWnd: indicates the reordering window should not be 100 | // adjusted when DSACK is received. 101 | // 102 | // TCPRACKNoDupTh: indicates RACK should not consider the classic three 103 | // duplicate acknowledgements rule to mark the segments as lost. This 104 | // is used when reordering is not detected. 105 | WithTCPRecovery(tcpRecovery), 106 | } 107 | 108 | for _, opt := range opts { 109 | if err := opt(s); err != nil { 110 | return err 111 | } 112 | } 113 | 114 | return nil 115 | } 116 | } 117 | 118 | // WithDefaultTTL sets the default TTL used by stack. 119 | func WithDefaultTTL(ttl uint8) Option { 120 | return func(s *stack.Stack) error { 121 | opt := tcpip.DefaultTTLOption(ttl) 122 | if err := s.SetNetworkProtocolOption(ipv4.ProtocolNumber, &opt); err != nil { 123 | return fmt.Errorf("set ipv4 default TTL: %s", err) 124 | } 125 | if err := s.SetNetworkProtocolOption(ipv6.ProtocolNumber, &opt); err != nil { 126 | return fmt.Errorf("set ipv6 default TTL: %s", err) 127 | } 128 | return nil 129 | } 130 | } 131 | 132 | // WithForwarding sets packet forwarding between NICs for IPv4 & IPv6. 133 | func WithForwarding(v bool) Option { 134 | return func(s *stack.Stack) error { 135 | if err := s.SetForwardingDefaultAndAllNICs(ipv4.ProtocolNumber, v); err != nil { 136 | return fmt.Errorf("set ipv4 forwarding: %s", err) 137 | } 138 | if err := s.SetForwardingDefaultAndAllNICs(ipv6.ProtocolNumber, v); err != nil { 139 | return fmt.Errorf("set ipv6 forwarding: %s", err) 140 | } 141 | return nil 142 | } 143 | } 144 | 145 | // WithICMPBurst sets the number of ICMP messages that can be sent 146 | // in a single burst. 147 | func WithICMPBurst(burst int) Option { 148 | return func(s *stack.Stack) error { 149 | s.SetICMPBurst(burst) 150 | return nil 151 | } 152 | } 153 | 154 | // WithICMPLimit sets the maximum number of ICMP messages permitted 155 | // by rate limiter. 156 | func WithICMPLimit(limit rate.Limit) Option { 157 | return func(s *stack.Stack) error { 158 | s.SetICMPLimit(limit) 159 | return nil 160 | } 161 | } 162 | 163 | // WithTCPSendBufferSize sets default the send buffer size for TCP. 164 | func WithTCPSendBufferSize(size int) Option { 165 | return func(s *stack.Stack) error { 166 | sndOpt := tcpip.TCPSendBufferSizeRangeOption{Min: tcpMinBufferSize, Default: size, Max: tcpMaxBufferSize} 167 | if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &sndOpt); err != nil { 168 | return fmt.Errorf("set TCP send buffer size range: %s", err) 169 | } 170 | return nil 171 | } 172 | } 173 | 174 | // WithTCPSendBufferSizeRange sets the send buffer size range for TCP. 175 | func WithTCPSendBufferSizeRange(a, b, c int) Option { 176 | return func(s *stack.Stack) error { 177 | sndOpt := tcpip.TCPSendBufferSizeRangeOption{Min: a, Default: b, Max: c} 178 | if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &sndOpt); err != nil { 179 | return fmt.Errorf("set TCP send buffer size range: %s", err) 180 | } 181 | return nil 182 | } 183 | } 184 | 185 | // WithTCPReceiveBufferSize sets the default receive buffer size for TCP. 186 | func WithTCPReceiveBufferSize(size int) Option { 187 | return func(s *stack.Stack) error { 188 | rcvOpt := tcpip.TCPReceiveBufferSizeRangeOption{Min: tcpMinBufferSize, Default: size, Max: tcpMaxBufferSize} 189 | if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &rcvOpt); err != nil { 190 | return fmt.Errorf("set TCP receive buffer size range: %s", err) 191 | } 192 | return nil 193 | } 194 | } 195 | 196 | // WithTCPReceiveBufferSizeRange sets the receive buffer size range for TCP. 197 | func WithTCPReceiveBufferSizeRange(a, b, c int) Option { 198 | return func(s *stack.Stack) error { 199 | rcvOpt := tcpip.TCPReceiveBufferSizeRangeOption{Min: a, Default: b, Max: c} 200 | if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &rcvOpt); err != nil { 201 | return fmt.Errorf("set TCP receive buffer size range: %s", err) 202 | } 203 | return nil 204 | } 205 | } 206 | 207 | // WithTCPCongestionControl sets the current congestion control algorithm. 208 | func WithTCPCongestionControl(cc string) Option { 209 | return func(s *stack.Stack) error { 210 | opt := tcpip.CongestionControlOption(cc) 211 | if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil { 212 | return fmt.Errorf("set TCP congestion control algorithm: %s", err) 213 | } 214 | return nil 215 | } 216 | } 217 | 218 | // WithTCPDelay enables or disables Nagle's algorithm in TCP. 219 | func WithTCPDelay(v bool) Option { 220 | return func(s *stack.Stack) error { 221 | opt := tcpip.TCPDelayEnabled(v) 222 | if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil { 223 | return fmt.Errorf("set TCP delay: %s", err) 224 | } 225 | return nil 226 | } 227 | } 228 | 229 | // WithTCPModerateReceiveBuffer sets receive buffer moderation for TCP. 230 | func WithTCPModerateReceiveBuffer(v bool) Option { 231 | return func(s *stack.Stack) error { 232 | opt := tcpip.TCPModerateReceiveBufferOption(v) 233 | if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil { 234 | return fmt.Errorf("set TCP moderate receive buffer: %s", err) 235 | } 236 | return nil 237 | } 238 | } 239 | 240 | // WithTCPSACKEnabled sets the SACK option for TCP. 241 | func WithTCPSACKEnabled(v bool) Option { 242 | return func(s *stack.Stack) error { 243 | opt := tcpip.TCPSACKEnabled(v) 244 | if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil { 245 | return fmt.Errorf("set TCP SACK: %s", err) 246 | } 247 | return nil 248 | } 249 | } 250 | 251 | // WithTCPRecovery sets the recovery option for TCP. 252 | func WithTCPRecovery(v tcpip.TCPRecovery) Option { 253 | return func(s *stack.Stack) error { 254 | if runtime.GOOS == "windows" { 255 | // See https://github.com/tailscale/tailscale/issues/9707 256 | // Windows w/RACK performs poorly. ACKs do not appear to be handled in a 257 | // timely manner, leading to spurious retransmissions and a reduced 258 | // congestion window. 259 | v = tcpip.TCPRecovery(0) 260 | } 261 | 262 | if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &v); err != nil { 263 | return fmt.Errorf("set TCP Recovery: %s", err) 264 | } 265 | return nil 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /core/route.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "github.com/DaniilSokolyuk/go-pcap2socks/core/option" 5 | "gvisor.dev/gvisor/pkg/tcpip" 6 | "gvisor.dev/gvisor/pkg/tcpip/header" 7 | "gvisor.dev/gvisor/pkg/tcpip/stack" 8 | ) 9 | 10 | func withRouteTable(nicID tcpip.NICID) option.Option { 11 | return func(s *stack.Stack) error { 12 | s.SetRouteTable([]tcpip.Route{ 13 | { 14 | Destination: header.IPv4EmptySubnet, 15 | NIC: nicID, 16 | }, 17 | { 18 | Destination: header.IPv6EmptySubnet, 19 | NIC: nicID, 20 | }, 21 | }) 22 | return nil 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /core/stack.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/DaniilSokolyuk/go-pcap2socks/core/adapter" 7 | "github.com/DaniilSokolyuk/go-pcap2socks/core/option" 8 | "gvisor.dev/gvisor/pkg/tcpip/network/arp" 9 | 10 | "gvisor.dev/gvisor/pkg/tcpip" 11 | "gvisor.dev/gvisor/pkg/tcpip/network/ipv4" 12 | "gvisor.dev/gvisor/pkg/tcpip/network/ipv6" 13 | "gvisor.dev/gvisor/pkg/tcpip/stack" 14 | "gvisor.dev/gvisor/pkg/tcpip/transport/icmp" 15 | "gvisor.dev/gvisor/pkg/tcpip/transport/tcp" 16 | "gvisor.dev/gvisor/pkg/tcpip/transport/udp" 17 | ) 18 | 19 | // Config is the configuration to create *stack.Stack. 20 | type Config struct { 21 | // LinkEndpoints is the interface implemented by 22 | // data link layer protocols. 23 | LinkEndpoint stack.LinkEndpoint 24 | 25 | // TransportHandler is the handler used by internal 26 | // stack to set transport handlers. 27 | TransportHandler adapter.TransportHandler 28 | 29 | // MulticastGroups is used by internal stack to add 30 | // nic to given groups. 31 | MulticastGroups []net.IP 32 | 33 | // Options are supplement options to apply settings 34 | // for the internal stack. 35 | Options []option.Option 36 | } 37 | 38 | var NicID tcpip.NICID 39 | 40 | // CreateStack creates *stack.Stack with given config. 41 | func CreateStack(cfg *Config) (*stack.Stack, error) { 42 | opts := []option.Option{option.WithDefault()} 43 | if len(opts) > 0 { 44 | opts = append(opts, cfg.Options...) 45 | } 46 | 47 | s := stack.New(stack.Options{ 48 | NetworkProtocols: []stack.NetworkProtocolFactory{ 49 | ipv4.NewProtocol, 50 | ipv6.NewProtocol, 51 | arp.NewProtocol, 52 | }, 53 | TransportProtocols: []stack.TransportProtocolFactory{ 54 | tcp.NewProtocol, 55 | udp.NewProtocol, 56 | icmp.NewProtocol4, 57 | icmp.NewProtocol6, 58 | }, 59 | }) 60 | 61 | // Generate unique NIC id. 62 | NicID = s.NextNICID() 63 | 64 | opts = append(opts, 65 | // Important: We must initiate transport protocol handlers 66 | // before creating NIC, otherwise NIC would dispatch packets 67 | // to stack and cause race condition. 68 | // Initiate transport protocol (TCP/UDP) with given handler. 69 | withTCPHandler(cfg.TransportHandler.HandleTCP), 70 | withUDPNatHandler(cfg.TransportHandler.HandleUDP), 71 | 72 | // Create stack NIC and then bind link endpoint to it. 73 | withCreatingNIC(NicID, cfg.LinkEndpoint), 74 | 75 | // In the past we did s.AddAddressRange to assign 0.0.0.0/0 76 | // onto the interface. We need that to be able to terminate 77 | // all the incoming connections - to any ip. AddressRange API 78 | // has been removed and the suggested workaround is to use 79 | // Promiscuous mode. https://github.com/google/gvisor/issues/3876 80 | // 81 | // Ref: https://github.com/cloudflare/slirpnetstack/blob/master/stack.go 82 | withPromiscuousMode(NicID, nicPromiscuousModeEnabled), 83 | 84 | // Enable spoofing if a stack may send packets from unowned 85 | // addresses. This change required changes to some netgophers 86 | // since previously, promiscuous mode was enough to let the 87 | // netstack respond to all incoming packets regardless of the 88 | // packet's destination address. Now that a stack.Route is not 89 | // held for each incoming packet, finding a route may fail with 90 | // local addresses we don't own but accepted packets for while 91 | // in promiscuous mode. Since we also want to be able to send 92 | // from any address (in response the received promiscuous mode 93 | // packets), we need to enable spoofing. 94 | // 95 | // Ref: https://github.com/google/gvisor/commit/8c0701462a84ff77e602f1626aec49479c308127 96 | withSpoofing(NicID, nicSpoofingEnabled), 97 | 98 | withRouteTable(NicID), 99 | ) 100 | 101 | for _, opt := range opts { 102 | if err := opt(s); err != nil { 103 | return nil, err 104 | } 105 | } 106 | return s, nil 107 | } 108 | -------------------------------------------------------------------------------- /core/tcp.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/DaniilSokolyuk/go-pcap2socks/core/adapter" 7 | "github.com/DaniilSokolyuk/go-pcap2socks/core/option" 8 | 9 | glog "gvisor.dev/gvisor/pkg/log" 10 | "gvisor.dev/gvisor/pkg/tcpip" 11 | "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet" 12 | "gvisor.dev/gvisor/pkg/tcpip/header" 13 | "gvisor.dev/gvisor/pkg/tcpip/stack" 14 | "gvisor.dev/gvisor/pkg/tcpip/transport/tcp" 15 | "gvisor.dev/gvisor/pkg/waiter" 16 | ) 17 | 18 | const ( 19 | // defaultWndSize if set to zero, the default 20 | // receive window buffer size is used instead. 21 | defaultWndSize = 0 22 | 23 | // maxConnAttempts specifies the maximum number 24 | // of in-flight tcp connection attempts. 25 | maxConnAttempts = 2 << 10 26 | 27 | // tcpKeepaliveCount is the maximum number of 28 | // TCP keep-alive probes to send before giving up 29 | // and killing the connection if no response is 30 | // obtained from the other end. 31 | tcpKeepaliveCount = 9 32 | 33 | // tcpKeepaliveIdle specifies the time a connection 34 | // must remain idle before the first TCP keepalive 35 | // packet is sent. Once this time is reached, 36 | // tcpKeepaliveInterval option is used instead. 37 | tcpKeepaliveIdle = 60 * time.Second 38 | 39 | // tcpKeepaliveInterval specifies the interval 40 | // time between sending TCP keepalive packets. 41 | tcpKeepaliveInterval = 30 * time.Second 42 | ) 43 | 44 | func withTCPHandler(handle func(adapter.TCPConn)) option.Option { 45 | return func(s *stack.Stack) error { 46 | tcpForwarder := tcp.NewForwarder(s, defaultWndSize, maxConnAttempts, func(r *tcp.ForwarderRequest) { 47 | var ( 48 | wq waiter.Queue 49 | ep tcpip.Endpoint 50 | err tcpip.Error 51 | id = r.ID() 52 | ) 53 | 54 | defer func() { 55 | if err != nil { 56 | glog.Debugf("forward tcp request: %s:%d->%s:%d: %s", 57 | id.RemoteAddress, id.RemotePort, id.LocalAddress, id.LocalPort, err) 58 | } 59 | }() 60 | 61 | // Perform a TCP three-way handshake. 62 | ep, err = r.CreateEndpoint(&wq) 63 | if err != nil { 64 | // RST: prevent potential half-open TCP connection leak. 65 | r.Complete(true) 66 | return 67 | } 68 | defer r.Complete(false) 69 | 70 | err = setSocketOptions(s, ep) 71 | 72 | conn := &tcpConn{ 73 | TCPConn: gonet.NewTCPConn(&wq, ep), 74 | id: id, 75 | } 76 | handle(conn) 77 | }) 78 | s.SetTransportProtocolHandler(tcp.ProtocolNumber, tcpForwarder.HandlePacket) 79 | return nil 80 | } 81 | } 82 | 83 | func setSocketOptions(s *stack.Stack, ep tcpip.Endpoint) tcpip.Error { 84 | { /* TCP keepalive options */ 85 | ep.SocketOptions().SetKeepAlive(true) 86 | 87 | idle := tcpip.KeepaliveIdleOption(tcpKeepaliveIdle) 88 | if err := ep.SetSockOpt(&idle); err != nil { 89 | return err 90 | } 91 | 92 | interval := tcpip.KeepaliveIntervalOption(tcpKeepaliveInterval) 93 | if err := ep.SetSockOpt(&interval); err != nil { 94 | return err 95 | } 96 | 97 | if err := ep.SetSockOptInt(tcpip.KeepaliveCountOption, tcpKeepaliveCount); err != nil { 98 | return err 99 | } 100 | } 101 | { /* TCP recv/send buffer size */ 102 | var ss tcpip.TCPSendBufferSizeRangeOption 103 | if err := s.TransportProtocolOption(header.TCPProtocolNumber, &ss); err == nil { 104 | ep.SocketOptions().SetSendBufferSize(int64(ss.Default), false) 105 | } 106 | 107 | var rs tcpip.TCPReceiveBufferSizeRangeOption 108 | if err := s.TransportProtocolOption(header.TCPProtocolNumber, &rs); err == nil { 109 | ep.SocketOptions().SetReceiveBufferSize(int64(rs.Default), false) 110 | } 111 | } 112 | return nil 113 | } 114 | 115 | type tcpConn struct { 116 | *gonet.TCPConn 117 | id stack.TransportEndpointID 118 | } 119 | 120 | func (c *tcpConn) ID() *stack.TransportEndpointID { 121 | return &c.id 122 | } 123 | -------------------------------------------------------------------------------- /core/tunnel.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "github.com/DaniilSokolyuk/go-pcap2socks/core/adapter" 5 | "github.com/DaniilSokolyuk/go-pcap2socks/tunnel" 6 | ) 7 | 8 | var _ adapter.TransportHandler = (*Tunnel)(nil) 9 | 10 | type Tunnel struct{} 11 | 12 | func (*Tunnel) HandleTCP(conn adapter.TCPConn) { 13 | tunnel.TCPIn() <- conn 14 | } 15 | 16 | func (t Tunnel) HandleUDP(conn adapter.UDPConn) { 17 | tunnel.HandleUDPConn(conn) 18 | } 19 | -------------------------------------------------------------------------------- /core/udpforwarder.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log/slog" 7 | "math" 8 | "net" 9 | "net/netip" 10 | "os" 11 | "sync" 12 | "time" 13 | 14 | "github.com/DaniilSokolyuk/go-pcap2socks/tunnel" 15 | "github.com/sagernet/sing/common" 16 | "github.com/sagernet/sing/common/udpnat" 17 | 18 | "github.com/DaniilSokolyuk/go-pcap2socks/core/adapter" 19 | "github.com/DaniilSokolyuk/go-pcap2socks/core/option" 20 | "github.com/DaniilSokolyuk/go-pcap2socks/md" 21 | MM "github.com/DaniilSokolyuk/go-pcap2socks/md" 22 | "github.com/sagernet/sing/common/buf" 23 | E "github.com/sagernet/sing/common/exceptions" 24 | M "github.com/sagernet/sing/common/metadata" 25 | N "github.com/sagernet/sing/common/network" 26 | "gvisor.dev/gvisor/pkg/buffer" 27 | "gvisor.dev/gvisor/pkg/tcpip" 28 | "gvisor.dev/gvisor/pkg/tcpip/checksum" 29 | "gvisor.dev/gvisor/pkg/tcpip/header" 30 | "gvisor.dev/gvisor/pkg/tcpip/stack" 31 | "gvisor.dev/gvisor/pkg/tcpip/transport/udp" 32 | ) 33 | 34 | type Handler interface { 35 | N.UDPConnectionHandler 36 | E.Handler 37 | } 38 | 39 | type handler struct { 40 | handle func(adapter.UDPConn) 41 | } 42 | 43 | func (h handler) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { 44 | h.handle(proxyHandler{meta: metadata, conn: conn}) 45 | return nil 46 | } 47 | 48 | func (h handler) NewError(ctx context.Context, err error) { 49 | slog.Error("udp PacketConnection proxy error: ", err) 50 | } 51 | 52 | func CreateProxyHandler(a func(adapter.UDPConn)) Handler { 53 | return &handler{ 54 | handle: a, 55 | } 56 | } 57 | 58 | type proxyHandler struct { 59 | conn N.PacketConn 60 | meta M.Metadata 61 | } 62 | 63 | func (ph proxyHandler) ReadFrom(p []byte) (n int, addr net.Addr, err error) { 64 | buffer := buf.With(p) 65 | destination, err := ph.conn.ReadPacket(buffer) 66 | if err != nil { 67 | slog.Error("udp read packet error: ", err) 68 | return 69 | } 70 | n = buffer.Len() 71 | if buffer.Start() > 0 { 72 | copy(p, buffer.Bytes()) 73 | } 74 | addr = destination.UDPAddr() 75 | return 76 | } 77 | 78 | func (ph proxyHandler) WriteTo(p []byte, addr net.Addr) (n int, err error) { 79 | bf := buf.NewSize(len(p)) 80 | common.Must1(bf.Write(p)) 81 | err = ph.conn.WritePacket(bf, M.SocksaddrFromNet(addr).Unwrap()) 82 | if err != nil { 83 | slog.Error("udp write packet error: ", err) 84 | return 0, err 85 | } 86 | 87 | return len(p), nil 88 | } 89 | 90 | func (ph proxyHandler) Close() error { 91 | return ph.conn.Close() 92 | } 93 | 94 | func (ph proxyHandler) LocalAddr() net.Addr { 95 | return ph.conn.LocalAddr() 96 | } 97 | 98 | func (ph proxyHandler) SetDeadline(t time.Time) error { 99 | return ph.conn.SetDeadline(t) 100 | } 101 | 102 | func (ph proxyHandler) SetReadDeadline(t time.Time) error { 103 | return ph.conn.SetReadDeadline(t) 104 | } 105 | 106 | func (ph proxyHandler) SetWriteDeadline(t time.Time) error { 107 | return ph.conn.SetWriteDeadline(t) 108 | } 109 | 110 | func (ph proxyHandler) MD() *metadata.Metadata { 111 | mh := &MM.Metadata{ 112 | Network: MM.UDP, 113 | SrcIP: net.IP(ph.meta.Source.Addr.AsSlice()), 114 | SrcPort: ph.meta.Source.Port, 115 | DstIP: net.IP(ph.meta.Destination.Addr.AsSlice()), 116 | DstPort: ph.meta.Destination.Port, 117 | } 118 | 119 | return mh 120 | } 121 | 122 | func withUDPNatHandler(handle func(adapter.UDPConn)) option.Option { 123 | return func(s *stack.Stack) error { 124 | udpForwarder := NewUDPForwarder(context.Background(), s, CreateProxyHandler(handle), int64(tunnel.UdpSessionTimeout.Seconds())) 125 | s.SetTransportProtocolHandler(udp.ProtocolNumber, udpForwarder.HandlePacket) 126 | return nil 127 | } 128 | } 129 | 130 | type UDPForwarder struct { 131 | ctx context.Context 132 | stack *stack.Stack 133 | udpNat *udpnat.Service[netip.AddrPort] 134 | 135 | // cache 136 | cacheProto tcpip.NetworkProtocolNumber 137 | cacheID stack.TransportEndpointID 138 | } 139 | 140 | func NewUDPForwarder(ctx context.Context, stack *stack.Stack, handler Handler, udpTimeout int64) *UDPForwarder { 141 | return &UDPForwarder{ 142 | ctx: ctx, 143 | stack: stack, 144 | udpNat: udpnat.New[netip.AddrPort](udpTimeout, handler), 145 | } 146 | } 147 | 148 | func (f *UDPForwarder) HandlePacket(id stack.TransportEndpointID, pkt *stack.PacketBuffer) bool { 149 | var upstreamMetadata M.Metadata 150 | upstreamMetadata.Source = M.SocksaddrFrom(AddrFromAddress(id.RemoteAddress), id.RemotePort) 151 | upstreamMetadata.Destination = M.SocksaddrFrom(AddrFromAddress(id.LocalAddress), id.LocalPort) 152 | if upstreamMetadata.Source.IsIPv4() { 153 | f.cacheProto = header.IPv4ProtocolNumber 154 | } else { 155 | f.cacheProto = header.IPv6ProtocolNumber 156 | } 157 | gBuffer := pkt.Data().ToBuffer() 158 | sBuffer := buf.NewSize(int(gBuffer.Size())) 159 | gBuffer.Apply(func(view *buffer.View) { 160 | sBuffer.Write(view.AsSlice()) 161 | }) 162 | f.cacheID = id 163 | f.udpNat.NewPacket( 164 | f.ctx, 165 | upstreamMetadata.Source.AddrPort(), 166 | sBuffer, 167 | upstreamMetadata, 168 | f.newUDPConn, 169 | ) 170 | return true 171 | } 172 | 173 | func (f *UDPForwarder) newUDPConn(natConn N.PacketConn) N.PacketWriter { 174 | return &UDPBackWriter{ 175 | stack: f.stack, 176 | source: f.cacheID.RemoteAddress, 177 | sourcePort: f.cacheID.RemotePort, 178 | sourceNetwork: f.cacheProto, 179 | } 180 | } 181 | 182 | type UDPBackWriter struct { 183 | access sync.Mutex 184 | stack *stack.Stack 185 | source tcpip.Address 186 | sourcePort uint16 187 | sourceNetwork tcpip.NetworkProtocolNumber 188 | } 189 | 190 | func (w *UDPBackWriter) WritePacket(packetBuffer *buf.Buffer, destination M.Socksaddr) error { 191 | if !destination.IsIP() { 192 | return E.Cause(os.ErrInvalid, "invalid destination") 193 | } else if destination.IsIPv4() && w.sourceNetwork == header.IPv6ProtocolNumber { 194 | destination = M.SocksaddrFrom(netip.AddrFrom16(destination.Addr.As16()), destination.Port) 195 | } else if destination.IsIPv6() && (w.sourceNetwork == header.IPv4ProtocolNumber) { 196 | return E.New("send IPv6 packet to IPv4 connection") 197 | } 198 | 199 | defer packetBuffer.Release() 200 | 201 | route, err := w.stack.FindRoute( 202 | NicID, 203 | AddressFromAddr(destination.Addr), 204 | w.source, 205 | w.sourceNetwork, 206 | false, 207 | ) 208 | if err != nil { 209 | return fmt.Errorf("find route: %s", err) 210 | } 211 | defer route.Release() 212 | 213 | packet := stack.NewPacketBuffer(stack.PacketBufferOptions{ 214 | ReserveHeaderBytes: header.UDPMinimumSize + int(route.MaxHeaderLength()), 215 | Payload: buffer.MakeWithData(packetBuffer.Bytes()), 216 | }) 217 | defer packet.DecRef() 218 | 219 | packet.TransportProtocolNumber = header.UDPProtocolNumber 220 | udpHdr := header.UDP(packet.TransportHeader().Push(header.UDPMinimumSize)) 221 | pLen := uint16(packet.Size()) 222 | udpHdr.Encode(&header.UDPFields{ 223 | SrcPort: destination.Port, 224 | DstPort: w.sourcePort, 225 | Length: pLen, 226 | }) 227 | 228 | if route.RequiresTXTransportChecksum() && w.sourceNetwork == header.IPv6ProtocolNumber { 229 | xsum := udpHdr.CalculateChecksum(checksum.Combine( 230 | route.PseudoHeaderChecksum(header.UDPProtocolNumber, pLen), 231 | packet.Data().Checksum(), 232 | )) 233 | if xsum != math.MaxUint16 { 234 | xsum = ^xsum 235 | } 236 | udpHdr.SetChecksum(xsum) 237 | } 238 | 239 | err = route.WritePacket(stack.NetworkHeaderParams{ 240 | Protocol: header.UDPProtocolNumber, 241 | TTL: route.DefaultTTL(), 242 | TOS: 0, 243 | }, packet) 244 | 245 | if err != nil { 246 | route.Stats().UDP.PacketSendErrors.Increment() 247 | return fmt.Errorf("write packet: %s", err) 248 | } 249 | 250 | route.Stats().UDP.PacketsSent.Increment() 251 | return nil 252 | } 253 | 254 | func AddrFromAddress(address tcpip.Address) netip.Addr { 255 | if address.Len() == 16 { 256 | return netip.AddrFrom16(address.As16()) 257 | } else { 258 | return netip.AddrFrom4(address.As4()) 259 | } 260 | } 261 | 262 | func AddressFromAddr(destination netip.Addr) tcpip.Address { 263 | if destination.Is6() { 264 | return tcpip.AddrFrom16(destination.As16()) 265 | } else { 266 | return tcpip.AddrFrom4(destination.As4()) 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /default.pgo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaniilSokolyuk/go-pcap2socks/068959fed5524694b495ca2ba977c7bdd9c2a081/default.pgo -------------------------------------------------------------------------------- /dialer/dialer.go: -------------------------------------------------------------------------------- 1 | package dialer 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "syscall" 7 | 8 | "go.uber.org/atomic" 9 | ) 10 | 11 | var ( 12 | DefaultInterfaceName = atomic.NewString("") 13 | DefaultInterfaceIndex = atomic.NewInt32(0) 14 | DefaultRoutingMark = atomic.NewInt32(0) 15 | ) 16 | 17 | type Options struct { 18 | // InterfaceName is the name of interface/device to bind. 19 | // If a socket is bound to an interface, only packets received 20 | // from that particular interface are processed by the socket. 21 | InterfaceName string 22 | 23 | // InterfaceIndex is the index of interface/device to bind. 24 | // It is almost the same as InterfaceName except it uses the 25 | // index of the interface instead of the name. 26 | InterfaceIndex int 27 | 28 | // RoutingMark is the mark for each packet sent through this 29 | // socket. Changing the mark can be used for mark-based routing 30 | // without netfilter or for packet filtering. 31 | RoutingMark int 32 | } 33 | 34 | func DialContext(ctx context.Context, network, address string) (net.Conn, error) { 35 | return DialContextWithOptions(ctx, network, address, &Options{ 36 | InterfaceName: DefaultInterfaceName.Load(), 37 | InterfaceIndex: int(DefaultInterfaceIndex.Load()), 38 | RoutingMark: int(DefaultRoutingMark.Load()), 39 | }) 40 | } 41 | 42 | func DialContextWithOptions(ctx context.Context, network, address string, opts *Options) (net.Conn, error) { 43 | d := &net.Dialer{ 44 | Control: func(network, address string, c syscall.RawConn) error { 45 | return setSocketOptions(network, address, c, opts) 46 | }, 47 | } 48 | return d.DialContext(ctx, network, address) 49 | } 50 | 51 | func ListenPacket(network, address string) (net.PacketConn, error) { 52 | return ListenPacketWithOptions(network, address, &Options{ 53 | InterfaceName: DefaultInterfaceName.Load(), 54 | InterfaceIndex: int(DefaultInterfaceIndex.Load()), 55 | RoutingMark: int(DefaultRoutingMark.Load()), 56 | }) 57 | } 58 | 59 | func ListenPacketWithOptions(network, address string, opts *Options) (net.PacketConn, error) { 60 | lc := &net.ListenConfig{ 61 | Control: func(network, address string, c syscall.RawConn) error { 62 | return setSocketOptions(network, address, c, opts) 63 | }, 64 | } 65 | return lc.ListenPacket(context.Background(), network, address) 66 | } 67 | -------------------------------------------------------------------------------- /dialer/sockopt.go: -------------------------------------------------------------------------------- 1 | package dialer 2 | 3 | func isTCPSocket(network string) bool { 4 | switch network { 5 | case "tcp", "tcp4", "tcp6": 6 | return true 7 | default: 8 | return false 9 | } 10 | } 11 | 12 | func isUDPSocket(network string) bool { 13 | switch network { 14 | case "udp", "udp4", "udp6": 15 | return true 16 | default: 17 | return false 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /dialer/sockopt_darwin.go: -------------------------------------------------------------------------------- 1 | package dialer 2 | 3 | import ( 4 | "net" 5 | "syscall" 6 | 7 | "golang.org/x/sys/unix" 8 | ) 9 | 10 | func setSocketOptions(network, address string, c syscall.RawConn, opts *Options) (err error) { 11 | if opts == nil || !isTCPSocket(network) && !isUDPSocket(network) { 12 | return 13 | } 14 | 15 | var innerErr error 16 | err = c.Control(func(fd uintptr) { 17 | host, _, _ := net.SplitHostPort(address) 18 | if ip := net.ParseIP(host); ip != nil && !ip.IsGlobalUnicast() { 19 | return 20 | } 21 | 22 | if opts.InterfaceIndex == 0 && opts.InterfaceName != "" { 23 | if iface, err := net.InterfaceByName(opts.InterfaceName); err == nil { 24 | opts.InterfaceIndex = iface.Index 25 | } 26 | } 27 | 28 | if opts.InterfaceIndex != 0 { 29 | switch network { 30 | case "tcp4", "udp4": 31 | innerErr = unix.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_BOUND_IF, opts.InterfaceIndex) 32 | case "tcp6", "udp6": 33 | innerErr = unix.SetsockoptInt(int(fd), syscall.IPPROTO_IPV6, syscall.IPV6_BOUND_IF, opts.InterfaceIndex) 34 | } 35 | if innerErr != nil { 36 | return 37 | } 38 | } 39 | }) 40 | 41 | if innerErr != nil { 42 | err = innerErr 43 | } 44 | return 45 | } 46 | -------------------------------------------------------------------------------- /dialer/sockopt_freebsd.go: -------------------------------------------------------------------------------- 1 | package dialer 2 | 3 | import ( 4 | "net" 5 | "syscall" 6 | 7 | "golang.org/x/sys/unix" 8 | ) 9 | 10 | func setSocketOptions(network, address string, c syscall.RawConn, opts *Options) (err error) { 11 | if opts == nil || !isTCPSocket(network) && !isUDPSocket(network) { 12 | return 13 | } 14 | 15 | var innerErr error 16 | err = c.Control(func(fd uintptr) { 17 | host, _, _ := net.SplitHostPort(address) 18 | if ip := net.ParseIP(host); ip != nil && !ip.IsGlobalUnicast() { 19 | return 20 | } 21 | 22 | if opts.RoutingMark != 0 { 23 | if innerErr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_USER_COOKIE, opts.RoutingMark); innerErr != nil { 24 | return 25 | } 26 | } 27 | }) 28 | 29 | if innerErr != nil { 30 | err = innerErr 31 | } 32 | return 33 | } 34 | -------------------------------------------------------------------------------- /dialer/sockopt_linux.go: -------------------------------------------------------------------------------- 1 | package dialer 2 | 3 | import ( 4 | "net" 5 | "syscall" 6 | 7 | "golang.org/x/sys/unix" 8 | ) 9 | 10 | func setSocketOptions(network, address string, c syscall.RawConn, opts *Options) (err error) { 11 | if opts == nil || !isTCPSocket(network) && !isUDPSocket(network) { 12 | return 13 | } 14 | 15 | var innerErr error 16 | err = c.Control(func(fd uintptr) { 17 | host, _, _ := net.SplitHostPort(address) 18 | if ip := net.ParseIP(host); ip != nil && !ip.IsGlobalUnicast() { 19 | return 20 | } 21 | 22 | if opts.InterfaceName == "" && opts.InterfaceIndex != 0 { 23 | if iface, err := net.InterfaceByIndex(opts.InterfaceIndex); err == nil { 24 | opts.InterfaceName = iface.Name 25 | } 26 | } 27 | 28 | if opts.InterfaceName != "" { 29 | if innerErr = unix.BindToDevice(int(fd), opts.InterfaceName); innerErr != nil { 30 | return 31 | } 32 | } 33 | if opts.RoutingMark != 0 { 34 | if innerErr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_MARK, opts.RoutingMark); innerErr != nil { 35 | return 36 | } 37 | } 38 | }) 39 | 40 | if innerErr != nil { 41 | err = innerErr 42 | } 43 | return 44 | } 45 | -------------------------------------------------------------------------------- /dialer/sockopt_openbsd.go: -------------------------------------------------------------------------------- 1 | package dialer 2 | 3 | import ( 4 | "net" 5 | "syscall" 6 | 7 | "golang.org/x/sys/unix" 8 | ) 9 | 10 | func setSocketOptions(network, address string, c syscall.RawConn, opts *Options) (err error) { 11 | if opts == nil || !isTCPSocket(network) && !isUDPSocket(network) { 12 | return 13 | } 14 | 15 | var innerErr error 16 | err = c.Control(func(fd uintptr) { 17 | host, _, _ := net.SplitHostPort(address) 18 | if ip := net.ParseIP(host); ip != nil && !ip.IsGlobalUnicast() { 19 | return 20 | } 21 | 22 | if opts.RoutingMark != 0 { 23 | if innerErr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_RTABLE, opts.RoutingMark); innerErr != nil { 24 | return 25 | } 26 | } 27 | }) 28 | 29 | if innerErr != nil { 30 | err = innerErr 31 | } 32 | return 33 | } 34 | -------------------------------------------------------------------------------- /dialer/sockopt_others.go: -------------------------------------------------------------------------------- 1 | //go:build !unix && !windows 2 | 3 | package dialer 4 | 5 | import "syscall" 6 | 7 | func setSocketOptions(network, address string, c syscall.RawConn, opts *Options) error { 8 | return nil 9 | } 10 | -------------------------------------------------------------------------------- /dialer/sockopt_windows.go: -------------------------------------------------------------------------------- 1 | package dialer 2 | 3 | import ( 4 | "encoding/binary" 5 | "net" 6 | "syscall" 7 | "unsafe" 8 | 9 | "golang.org/x/sys/windows" 10 | ) 11 | 12 | const ( 13 | IP_UNICAST_IF = 31 14 | IPV6_UNICAST_IF = 31 15 | ) 16 | 17 | func setSocketOptions(network, address string, c syscall.RawConn, opts *Options) (err error) { 18 | if opts == nil || !isTCPSocket(network) && !isUDPSocket(network) { 19 | return 20 | } 21 | 22 | var innerErr error 23 | err = c.Control(func(fd uintptr) { 24 | host, _, _ := net.SplitHostPort(address) 25 | ip := net.ParseIP(host) 26 | if ip != nil && !ip.IsGlobalUnicast() { 27 | return 28 | } 29 | 30 | if opts.InterfaceIndex == 0 && opts.InterfaceName != "" { 31 | if iface, err := net.InterfaceByName(opts.InterfaceName); err == nil { 32 | opts.InterfaceIndex = iface.Index 33 | } 34 | } 35 | 36 | if opts.InterfaceIndex != 0 { 37 | switch network { 38 | case "tcp4", "udp4": 39 | innerErr = bindSocketToInterface4(windows.Handle(fd), uint32(opts.InterfaceIndex)) 40 | case "tcp6", "udp6": 41 | innerErr = bindSocketToInterface6(windows.Handle(fd), uint32(opts.InterfaceIndex)) 42 | if network == "udp6" && ip == nil { 43 | // The underlying IP net maybe IPv4 even if the `network` param is `udp6`, 44 | // so we should bind socket to interface4 at the same time. 45 | innerErr = bindSocketToInterface4(windows.Handle(fd), uint32(opts.InterfaceIndex)) 46 | } 47 | } 48 | } 49 | }) 50 | 51 | if innerErr != nil { 52 | err = innerErr 53 | } 54 | return 55 | } 56 | 57 | func bindSocketToInterface4(handle windows.Handle, index uint32) error { 58 | // For IPv4, this parameter must be an interface index in network byte order. 59 | // Ref: https://learn.microsoft.com/en-us/windows/win32/winsock/ipproto-ip-socket-options 60 | var bytes [4]byte 61 | binary.BigEndian.PutUint32(bytes[:], index) 62 | index = *(*uint32)(unsafe.Pointer(&bytes[0])) 63 | return windows.SetsockoptInt(handle, windows.IPPROTO_IP, IP_UNICAST_IF, int(index)) 64 | } 65 | 66 | func bindSocketToInterface6(handle windows.Handle, index uint32) error { 67 | return windows.SetsockoptInt(handle, windows.IPPROTO_IPV6, IPV6_UNICAST_IF, int(index)) 68 | } 69 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/DaniilSokolyuk/go-pcap2socks 2 | 3 | go 1.22.1 4 | 5 | require ( 6 | github.com/gopacket/gopacket v1.2.0 7 | github.com/jackpal/gateway v1.0.15 8 | github.com/sagernet/sing v0.4.2 9 | github.com/stretchr/testify v1.9.0 10 | go.uber.org/atomic v1.11.0 11 | golang.org/x/sys v0.22.0 12 | golang.org/x/time v0.5.0 13 | gvisor.dev/gvisor v0.0.0-20240726212243-a2b0498dbe7d 14 | ) 15 | 16 | require ( 17 | github.com/davecgh/go-spew v1.1.1 // indirect 18 | github.com/google/btree v1.1.2 // indirect 19 | github.com/pmezard/go-difflib v1.0.0 // indirect 20 | github.com/stretchr/objx v0.5.2 // indirect 21 | golang.org/x/net v0.27.0 // indirect 22 | gopkg.in/yaml.v3 v3.0.1 // indirect 23 | ) 24 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= 4 | github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= 5 | github.com/gopacket/gopacket v1.2.0 h1:eXbzFad7f73P1n2EJHQlsKuvIMJjVXK5tXoSca78I3A= 6 | github.com/gopacket/gopacket v1.2.0/go.mod h1:BrAKEy5EOGQ76LSqh7DMAr7z0NNPdczWm2GxCG7+I8M= 7 | github.com/jackpal/gateway v1.0.15 h1:yb4Gltgr8ApHWWnSyybnDL1vURbqw7ooo7IIL5VZSeg= 8 | github.com/jackpal/gateway v1.0.15/go.mod h1:dbyEDcDhHUh9EmjB9ung81elMUZfG0SoNc2TfTbcj4c= 9 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 10 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 11 | github.com/sagernet/sing v0.4.2 h1:jzGNJdZVRI0xlAfFugsIQUPvyB9SuWvbJK7zQCXc4QM= 12 | github.com/sagernet/sing v0.4.2/go.mod h1:ieZHA/+Y9YZfXs2I3WtuwgyCZ6GPsIR7HdKb1SdEnls= 13 | github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= 14 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 15 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 16 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 17 | go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= 18 | go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= 19 | golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= 20 | golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= 21 | golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= 22 | golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 23 | golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= 24 | golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 25 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 26 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 27 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 28 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 29 | gvisor.dev/gvisor v0.0.0-20240726212243-a2b0498dbe7d h1:nF+dSOz0u0DLrhnOmGp3ocPAylsgpim29DGIt/oxNR4= 30 | gvisor.dev/gvisor v0.0.0-20240726212243-a2b0498dbe7d/go.mod h1:sxc3Uvk/vHcd3tj7/DHVBoR5wvWT/MmRq2pj7HRJnwU= 31 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | _ "embed" 5 | "fmt" 6 | "log" 7 | "log/slog" 8 | "net" 9 | "net/http" 10 | _ "net/http/pprof" 11 | "os" 12 | "os/exec" 13 | "path" 14 | 15 | "github.com/DaniilSokolyuk/go-pcap2socks/cfg" 16 | "github.com/DaniilSokolyuk/go-pcap2socks/core" 17 | "github.com/DaniilSokolyuk/go-pcap2socks/core/device" 18 | "github.com/DaniilSokolyuk/go-pcap2socks/core/option" 19 | "github.com/DaniilSokolyuk/go-pcap2socks/proxy" 20 | "gvisor.dev/gvisor/pkg/tcpip/stack" 21 | ) 22 | 23 | //go:embed config.json 24 | var configData string 25 | 26 | func main() { 27 | // get config file from first argument or use config.json 28 | var cfgFile string 29 | if len(os.Args) > 1 { 30 | cfgFile = os.Args[1] 31 | } else { 32 | executable, err := os.Executable() 33 | if err != nil { 34 | slog.Error("get executable error", "error", err) 35 | return 36 | } 37 | 38 | cfgFile = path.Join(path.Dir(executable), "config.json") 39 | } 40 | 41 | cfgExists := cfg.Exists(cfgFile) 42 | if !cfgExists { 43 | slog.Info("Config file not found, creating a new one", "file", cfgFile) 44 | //path to near executable file 45 | err := os.WriteFile(cfgFile, []byte(configData), 0666) 46 | if err != nil { 47 | slog.Error("write config error", "file", cfgFile, "error", err) 48 | return 49 | } 50 | } 51 | 52 | config, err := cfg.Load(cfgFile) 53 | if err != nil { 54 | slog.Error("load config error", "file", cfgFile, "error", err) 55 | return 56 | } 57 | slog.Info("Config loaded", "file", cfgFile) 58 | 59 | if len(config.ExecuteOnStart) > 0 { 60 | slog.Info("Executing commands on start", "cmd", config.ExecuteOnStart) 61 | 62 | var cmd *exec.Cmd 63 | if len(config.ExecuteOnStart) > 1 { 64 | cmd = exec.Command(config.ExecuteOnStart[0], config.ExecuteOnStart[1:]...) 65 | } else { 66 | cmd = exec.Command(config.ExecuteOnStart[0]) 67 | } 68 | 69 | cmd.Stdout = os.Stdout 70 | cmd.Stderr = os.Stderr 71 | 72 | go func() { 73 | err := cmd.Start() 74 | if err != nil { 75 | slog.Error("execute command error", "error", err) 76 | } 77 | 78 | err = cmd.Wait() 79 | if err != nil { 80 | 81 | } 82 | }() 83 | } 84 | 85 | err = run(config) 86 | if err != nil { 87 | slog.Error("run error", "error", err) 88 | return 89 | } 90 | 91 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 92 | w.Write([]byte("Hello world!")) 93 | }) 94 | log.Fatal(http.ListenAndServe(":8080", nil)) 95 | } 96 | 97 | func run(cfg *cfg.Config) error { 98 | proxies := make(map[string]proxy.Proxy) 99 | var err error 100 | for _, outbound := range cfg.Outbounds { 101 | var p proxy.Proxy 102 | switch { 103 | case outbound.Direct != nil: 104 | p = proxy.NewDirect() 105 | case outbound.Socks != nil: 106 | p, err = proxy.NewSocks5(outbound.Socks.Address, outbound.Socks.Username, outbound.Socks.Password) 107 | if err != nil { 108 | return fmt.Errorf("new socks5 error: %w", err) 109 | } 110 | case outbound.Reject != nil: 111 | p = proxy.NewReject() 112 | default: 113 | return fmt.Errorf("invalid outbound: %+v", outbound) 114 | } 115 | 116 | proxies[outbound.Tag] = p 117 | } 118 | 119 | _defaultProxy = proxy.NewRouter(cfg.Routing.Rules, proxies) 120 | proxy.SetDialer(_defaultProxy) 121 | 122 | _defaultDevice, err = device.Open(cfg.PCAP, func() device.Stacker { 123 | return _defaultStack 124 | }) 125 | if err != nil { 126 | return err 127 | } 128 | 129 | if _defaultStack, err = core.CreateStack(&core.Config{ 130 | LinkEndpoint: _defaultDevice, 131 | TransportHandler: &core.Tunnel{}, 132 | MulticastGroups: []net.IP{}, 133 | Options: []option.Option{}, 134 | }); err != nil { 135 | slog.Error("create stack error: %w", err) 136 | } 137 | 138 | return nil 139 | } 140 | 141 | var ( 142 | // _defaultProxy holds the default proxy for the engine. 143 | _defaultProxy proxy.Proxy 144 | 145 | // _defaultDevice holds the default device for the engine. 146 | _defaultDevice device.Device 147 | 148 | // _defaultStack holds the default stack for the engine. 149 | _defaultStack *stack.Stack 150 | ) 151 | -------------------------------------------------------------------------------- /md/metadata.go: -------------------------------------------------------------------------------- 1 | package metadata 2 | 3 | import ( 4 | "net" 5 | "strconv" 6 | ) 7 | 8 | // Metadata contains metadata of transport protocol sessions. 9 | type Metadata struct { 10 | Network Network `json:"network"` 11 | SrcIP net.IP `json:"sourceIP"` 12 | MidIP net.IP `json:"dialerIP"` 13 | DstIP net.IP `json:"destinationIP"` 14 | SrcPort uint16 `json:"sourcePort"` 15 | MidPort uint16 `json:"dialerPort"` 16 | DstPort uint16 `json:"destinationPort"` 17 | } 18 | 19 | func (m *Metadata) DestinationAddress() string { 20 | return net.JoinHostPort(m.DstIP.String(), strconv.FormatUint(uint64(m.DstPort), 10)) 21 | } 22 | 23 | func (m *Metadata) SourceAddress() string { 24 | return net.JoinHostPort(m.SrcIP.String(), strconv.FormatUint(uint64(m.SrcPort), 10)) 25 | } 26 | 27 | func (m *Metadata) Addr() net.Addr { 28 | return &Addr{metadata: m} 29 | } 30 | 31 | func (m *Metadata) TCPAddr() *net.TCPAddr { 32 | if m.Network != TCP || m.DstIP == nil { 33 | return nil 34 | } 35 | return &net.TCPAddr{ 36 | IP: m.DstIP, 37 | Port: int(m.DstPort), 38 | } 39 | } 40 | 41 | func (m *Metadata) UDPAddr() *net.UDPAddr { 42 | if m.Network != UDP || m.DstIP == nil { 43 | return nil 44 | } 45 | return &net.UDPAddr{ 46 | IP: m.DstIP, 47 | Port: int(m.DstPort), 48 | } 49 | } 50 | 51 | // Addr implements the net.Addr interface. 52 | type Addr struct { 53 | metadata *Metadata 54 | } 55 | 56 | func (a *Addr) Metadata() *Metadata { 57 | return a.metadata 58 | } 59 | 60 | func (a *Addr) Network() string { 61 | return a.metadata.Network.String() 62 | } 63 | 64 | func (a *Addr) String() string { 65 | return a.metadata.DestinationAddress() 66 | } 67 | -------------------------------------------------------------------------------- /md/network.go: -------------------------------------------------------------------------------- 1 | package metadata 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | const ( 8 | TCP Network = iota 9 | UDP 10 | ) 11 | 12 | type Network uint8 13 | 14 | func (n Network) String() string { 15 | switch n { 16 | case TCP: 17 | return "tcp" 18 | case UDP: 19 | return "udp" 20 | default: 21 | return fmt.Sprintf("network(%d)", n) 22 | } 23 | } 24 | 25 | func (n Network) MarshalText() ([]byte, error) { 26 | return []byte(n.String()), nil 27 | } 28 | -------------------------------------------------------------------------------- /proxy/base.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "net" 7 | 8 | M "github.com/DaniilSokolyuk/go-pcap2socks/md" 9 | ) 10 | 11 | var _ Proxy = (*Base)(nil) 12 | 13 | type Base struct { 14 | addr string 15 | mode Mode 16 | } 17 | 18 | func (b *Base) Addr() string { 19 | return b.addr 20 | } 21 | 22 | func (b *Base) Mode() Mode { 23 | return b.mode 24 | } 25 | 26 | func (b *Base) DialContext(context.Context, *M.Metadata) (net.Conn, error) { 27 | return nil, errors.ErrUnsupported 28 | } 29 | 30 | func (b *Base) DialUDP(*M.Metadata) (net.PacketConn, error) { 31 | return nil, errors.ErrUnsupported 32 | } 33 | -------------------------------------------------------------------------------- /proxy/direct.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "context" 5 | "net" 6 | 7 | "github.com/DaniilSokolyuk/go-pcap2socks/dialer" 8 | M "github.com/DaniilSokolyuk/go-pcap2socks/md" 9 | ) 10 | 11 | var _ Proxy = (*Direct)(nil) 12 | 13 | type Direct struct { 14 | *Base 15 | } 16 | 17 | func NewDirect() *Direct { 18 | return &Direct{ 19 | Base: &Base{ 20 | mode: ModeDirect, 21 | }, 22 | } 23 | } 24 | 25 | func (d *Direct) DialContext(ctx context.Context, metadata *M.Metadata) (net.Conn, error) { 26 | c, err := dialer.DialContext(ctx, "tcp", metadata.DestinationAddress()) 27 | if err != nil { 28 | return nil, err 29 | } 30 | setKeepAlive(c) 31 | return c, nil 32 | } 33 | 34 | func (d *Direct) DialUDP(*M.Metadata) (net.PacketConn, error) { 35 | pc, err := dialer.ListenPacket("udp", "") 36 | if err != nil { 37 | return nil, err 38 | } 39 | return &directPacketConn{PacketConn: pc}, nil 40 | } 41 | 42 | type directPacketConn struct { 43 | net.PacketConn 44 | } 45 | 46 | func (pc *directPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) { 47 | if udpAddr, ok := addr.(*net.UDPAddr); ok { 48 | return pc.PacketConn.WriteTo(b, udpAddr) 49 | } 50 | 51 | udpAddr, err := net.ResolveUDPAddr("udp", addr.String()) 52 | if err != nil { 53 | return 0, err 54 | } 55 | return pc.PacketConn.WriteTo(b, udpAddr) 56 | } 57 | -------------------------------------------------------------------------------- /proxy/mode.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import "fmt" 4 | 5 | const ( 6 | ModeDirect Mode = iota 7 | ModeSocks5 8 | ModeRouter 9 | ModeReject 10 | ) 11 | 12 | type Mode uint8 13 | 14 | func (mode Mode) String() string { 15 | switch mode { 16 | case ModeRouter: 17 | return "router" 18 | case ModeDirect: 19 | return "direct" 20 | case ModeSocks5: 21 | return "socks5" 22 | case ModeReject: 23 | return "reject" 24 | default: 25 | return fmt.Sprintf("proto(%d)", mode) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /proxy/proxy.go: -------------------------------------------------------------------------------- 1 | // Package proxy provides implementations of proxy protocols. 2 | package proxy 3 | 4 | import ( 5 | "context" 6 | "net" 7 | "time" 8 | 9 | M "github.com/DaniilSokolyuk/go-pcap2socks/md" 10 | ) 11 | 12 | const ( 13 | tcpConnectTimeout = 5 * time.Second 14 | ) 15 | 16 | var _defaultDialer Dialer = &Base{} 17 | 18 | type Dialer interface { 19 | DialContext(context.Context, *M.Metadata) (net.Conn, error) 20 | DialUDP(*M.Metadata) (net.PacketConn, error) 21 | } 22 | 23 | type Proxy interface { 24 | Dialer 25 | Addr() string 26 | Mode() Mode 27 | } 28 | 29 | // SetDialer sets default Dialer. 30 | func SetDialer(d Dialer) { 31 | _defaultDialer = d 32 | } 33 | 34 | // Dial uses default Dialer to dial TCP. 35 | func Dial(metadata *M.Metadata) (net.Conn, error) { 36 | ctx, cancel := context.WithTimeout(context.Background(), tcpConnectTimeout) 37 | defer cancel() 38 | return _defaultDialer.DialContext(ctx, metadata) 39 | } 40 | 41 | // DialContext uses default Dialer to dial TCP with context. 42 | func DialContext(ctx context.Context, metadata *M.Metadata) (net.Conn, error) { 43 | return _defaultDialer.DialContext(ctx, metadata) 44 | } 45 | 46 | // DialUDP uses default Dialer to dial UDP. 47 | func DialUDP(metadata *M.Metadata) (net.PacketConn, error) { 48 | return _defaultDialer.DialUDP(metadata) 49 | } 50 | -------------------------------------------------------------------------------- /proxy/reject.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "log/slog" 7 | "net" 8 | "time" 9 | 10 | M "github.com/DaniilSokolyuk/go-pcap2socks/md" 11 | ) 12 | 13 | var _ Proxy = (*Reject)(nil) 14 | 15 | type Reject struct { 16 | *Base 17 | } 18 | 19 | func NewReject() *Reject { 20 | return &Reject{ 21 | Base: &Base{ 22 | mode: ModeReject, 23 | }, 24 | } 25 | } 26 | 27 | func (r *Reject) DialContext(_ context.Context, metadata *M.Metadata) (net.Conn, error) { 28 | slog.Info("[REJECT TCP]", "source", metadata.SourceAddress(), "dest", metadata.DestinationAddress()) 29 | return &nopConn{}, nil 30 | } 31 | 32 | func (r *Reject) DialUDP(metadata *M.Metadata) (net.PacketConn, error) { 33 | slog.Info("[REJECT UDP]", "source", metadata.SourceAddress(), "dest", metadata.DestinationAddress()) 34 | return &nopPacketConn{}, nil 35 | } 36 | 37 | type nopConn struct{} 38 | 39 | func (rw *nopConn) Read([]byte) (int, error) { return 0, io.EOF } 40 | func (rw *nopConn) Write([]byte) (int, error) { return 0, io.EOF } 41 | func (rw *nopConn) Close() error { return nil } 42 | func (rw *nopConn) LocalAddr() net.Addr { return nil } 43 | func (rw *nopConn) RemoteAddr() net.Addr { return nil } 44 | func (rw *nopConn) SetDeadline(time.Time) error { return nil } 45 | func (rw *nopConn) SetReadDeadline(time.Time) error { return nil } 46 | func (rw *nopConn) SetWriteDeadline(time.Time) error { return nil } 47 | 48 | type nopPacketConn struct{} 49 | 50 | func (npc *nopPacketConn) WriteTo(b []byte, _ net.Addr) (n int, err error) { return len(b), nil } 51 | func (npc *nopPacketConn) ReadFrom([]byte) (int, net.Addr, error) { return 0, nil, io.EOF } 52 | func (npc *nopPacketConn) Close() error { return nil } 53 | func (npc *nopPacketConn) LocalAddr() net.Addr { return &net.UDPAddr{IP: net.IPv4zero, Port: 0} } 54 | func (npc *nopPacketConn) SetDeadline(time.Time) error { return nil } 55 | func (npc *nopPacketConn) SetReadDeadline(time.Time) error { return nil } 56 | func (npc *nopPacketConn) SetWriteDeadline(time.Time) error { return nil } 57 | -------------------------------------------------------------------------------- /proxy/router.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net" 7 | 8 | "github.com/DaniilSokolyuk/go-pcap2socks/cfg" 9 | M "github.com/DaniilSokolyuk/go-pcap2socks/md" 10 | ) 11 | 12 | var _ Proxy = (*Router)(nil) 13 | 14 | type Router struct { 15 | *Base 16 | Rules []cfg.Rule 17 | Proxies map[string]Proxy 18 | } 19 | 20 | func NewRouter(rules []cfg.Rule, proxies map[string]Proxy) *Router { 21 | return &Router{ 22 | Rules: rules, 23 | Proxies: proxies, 24 | Base: &Base{ 25 | mode: ModeRouter, 26 | }, 27 | } 28 | } 29 | 30 | func (d *Router) DialContext(ctx context.Context, metadata *M.Metadata) (net.Conn, error) { 31 | for _, rule := range d.Rules { 32 | if match(metadata, rule) { 33 | if proxy, ok := d.Proxies[rule.OutboundTag]; ok { 34 | return proxy.DialContext(ctx, metadata) 35 | } 36 | 37 | return nil, fmt.Errorf("proxy %s not found", rule.OutboundTag) 38 | } 39 | } 40 | 41 | return d.Proxies[""].DialContext(ctx, metadata) 42 | } 43 | 44 | func (d *Router) DialUDP(metadata *M.Metadata) (net.PacketConn, error) { 45 | for _, rule := range d.Rules { 46 | if match(metadata, rule) { 47 | if proxy, ok := d.Proxies[rule.OutboundTag]; ok { 48 | return proxy.DialUDP(metadata) 49 | } 50 | 51 | return nil, fmt.Errorf("proxy %s not found", rule.OutboundTag) 52 | } 53 | } 54 | 55 | return d.Proxies[""].DialUDP(metadata) 56 | } 57 | 58 | func match(metadata *M.Metadata, rule cfg.Rule) bool { 59 | if _, ok := rule.SrcPorts[metadata.SrcPort]; ok { 60 | return true 61 | } 62 | if _, ok := rule.DstPorts[metadata.DstPort]; ok { 63 | return true 64 | } 65 | for _, ip := range rule.SrcIPs { 66 | if ip.Contains(metadata.SrcIP) { 67 | return true 68 | } 69 | } 70 | for _, ip := range rule.DstIPs { 71 | if ip.Contains(metadata.DstIP) { 72 | return true 73 | } 74 | } 75 | 76 | return false 77 | } 78 | -------------------------------------------------------------------------------- /proxy/socks5.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "net" 9 | 10 | "github.com/DaniilSokolyuk/go-pcap2socks/dialer" 11 | M "github.com/DaniilSokolyuk/go-pcap2socks/md" 12 | socks5 "github.com/DaniilSokolyuk/go-pcap2socks/transport" 13 | ) 14 | 15 | var _ Proxy = (*Socks5)(nil) 16 | 17 | type Socks5 struct { 18 | *Base 19 | 20 | user string 21 | pass string 22 | 23 | // unix indicates if socks5 over UDS is enabled. 24 | unix bool 25 | } 26 | 27 | func NewSocks5(addr, user, pass string) (*Socks5, error) { 28 | return &Socks5{ 29 | Base: &Base{ 30 | addr: addr, 31 | mode: ModeSocks5, 32 | }, 33 | user: user, 34 | pass: pass, 35 | unix: len(addr) > 0 && addr[0] == '/', 36 | }, nil 37 | } 38 | 39 | func (ss *Socks5) DialContext(ctx context.Context, metadata *M.Metadata) (c net.Conn, err error) { 40 | network := "tcp" 41 | if ss.unix { 42 | network = "unix" 43 | } 44 | 45 | c, err = dialer.DialContext(ctx, network, ss.Addr()) 46 | if err != nil { 47 | return nil, fmt.Errorf("connect to %s: %w", ss.Addr(), err) 48 | } 49 | setKeepAlive(c) 50 | 51 | defer func(c net.Conn) { 52 | safeConnClose(c, err) 53 | }(c) 54 | 55 | var user *socks5.User 56 | if ss.user != "" { 57 | user = &socks5.User{ 58 | Username: ss.user, 59 | Password: ss.pass, 60 | } 61 | } 62 | 63 | _, err = socks5.ClientHandshake(c, serializeSocksAddr(metadata), socks5.CmdConnect, user) 64 | return 65 | } 66 | 67 | func (ss *Socks5) DialUDP(*M.Metadata) (_ net.PacketConn, err error) { 68 | if ss.unix { 69 | return nil, fmt.Errorf("%w when unix domain socket is enabled", errors.ErrUnsupported) 70 | } 71 | 72 | ctx, cancel := context.WithTimeout(context.Background(), tcpConnectTimeout) 73 | defer cancel() 74 | 75 | c, err := dialer.DialContext(ctx, "tcp", ss.Addr()) 76 | if err != nil { 77 | err = fmt.Errorf("connect to %s: %w", ss.Addr(), err) 78 | return 79 | } 80 | setKeepAlive(c) 81 | 82 | defer func() { 83 | if err != nil && c != nil { 84 | c.Close() 85 | } 86 | }() 87 | 88 | var user *socks5.User 89 | if ss.user != "" { 90 | user = &socks5.User{ 91 | Username: ss.user, 92 | Password: ss.pass, 93 | } 94 | } 95 | 96 | // The UDP ASSOCIATE request is used to establish an association within 97 | // the UDP relay process to handle UDP datagrams. The DST.ADDR and 98 | // DST.PORT fields contain the address and port that the client expects 99 | // to use to send UDP datagrams on for the association. The server MAY 100 | // use this information to limit access to the association. If the 101 | // client is not in possession of the information at the time of the UDP 102 | // ASSOCIATE, the client MUST use a port number and address of all 103 | // zeros. RFC1928 104 | var targetAddr socks5.Addr = []byte{socks5.AtypIPv4, 0, 0, 0, 0, 0, 0} 105 | 106 | addr, err := socks5.ClientHandshake(c, targetAddr, socks5.CmdUDPAssociate, user) 107 | if err != nil { 108 | return nil, fmt.Errorf("client handshake: %w", err) 109 | } 110 | 111 | pc, err := dialer.ListenPacket("udp", "") 112 | if err != nil { 113 | return nil, fmt.Errorf("listen packet: %w", err) 114 | } 115 | 116 | go func() { 117 | io.Copy(io.Discard, c) 118 | c.Close() 119 | // A UDP association terminates when the TCP connection that the UDP 120 | // ASSOCIATE request arrived on terminates. RFC1928 121 | pc.Close() 122 | }() 123 | 124 | bindAddr := addr.UDPAddr() 125 | if bindAddr == nil { 126 | return nil, fmt.Errorf("invalid UDP binding address: %#v", addr) 127 | } 128 | 129 | if bindAddr.IP.IsUnspecified() { /* e.g. "0.0.0.0" or "::" */ 130 | udpAddr, err := net.ResolveUDPAddr("udp", ss.Addr()) 131 | if err != nil { 132 | return nil, fmt.Errorf("resolve udp address %s: %w", ss.Addr(), err) 133 | } 134 | bindAddr.IP = udpAddr.IP 135 | } 136 | 137 | return &socksPacketConn{PacketConn: pc, rAddr: bindAddr, tcpConn: c}, nil 138 | } 139 | 140 | type socksPacketConn struct { 141 | net.PacketConn 142 | 143 | rAddr net.Addr 144 | tcpConn net.Conn 145 | } 146 | 147 | func (pc *socksPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { 148 | var packet []byte 149 | if ma, ok := addr.(*M.Addr); ok { 150 | packet, err = socks5.EncodeUDPPacket(serializeSocksAddr(ma.Metadata()), b) 151 | } else { 152 | packet, err = socks5.EncodeUDPPacket(socks5.ParseAddr(addr), b) 153 | } 154 | 155 | if err != nil { 156 | return 157 | } 158 | return pc.PacketConn.WriteTo(packet, pc.rAddr) 159 | } 160 | 161 | func (pc *socksPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { 162 | n, _, err := pc.PacketConn.ReadFrom(b) 163 | if err != nil { 164 | return 0, nil, err 165 | } 166 | 167 | addr, payload, err := socks5.DecodeUDPPacket(b) 168 | if err != nil { 169 | return 0, nil, err 170 | } 171 | 172 | udpAddr := addr.UDPAddr() 173 | if udpAddr == nil { 174 | return 0, nil, fmt.Errorf("convert %s to UDPAddr is nil", addr) 175 | } 176 | 177 | // due to DecodeUDPPacket is mutable, record addr length 178 | copy(b, payload) 179 | return n - len(addr) - 3, udpAddr, nil 180 | } 181 | 182 | func (pc *socksPacketConn) Close() error { 183 | pc.tcpConn.Close() 184 | return pc.PacketConn.Close() 185 | } 186 | 187 | func serializeSocksAddr(m *M.Metadata) socks5.Addr { 188 | return socks5.SerializeAddr("", m.DstIP, m.DstPort) 189 | } 190 | -------------------------------------------------------------------------------- /proxy/util.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "net" 5 | "time" 6 | ) 7 | 8 | const ( 9 | tcpKeepAlivePeriod = 30 * time.Second 10 | ) 11 | 12 | // setKeepAlive sets tcp keepalive option for tcp connection. 13 | func setKeepAlive(c net.Conn) { 14 | if tcp, ok := c.(*net.TCPConn); ok { 15 | tcp.SetKeepAlive(true) 16 | tcp.SetKeepAlivePeriod(tcpKeepAlivePeriod) 17 | } 18 | } 19 | 20 | // safeConnClose closes tcp connection safely. 21 | func safeConnClose(c net.Conn, err error) { 22 | if c != nil && err != nil { 23 | c.Close() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /transport/socks5.go: -------------------------------------------------------------------------------- 1 | // Package socks5 provides SOCKS5 client functionalities. 2 | package socks5 3 | 4 | import ( 5 | "bytes" 6 | "encoding/binary" 7 | "errors" 8 | "fmt" 9 | "io" 10 | "net" 11 | "strconv" 12 | ) 13 | 14 | // AuthMethod is the authentication method as defined in RFC 1928 section 3. 15 | type AuthMethod = uint8 16 | 17 | // SOCKS authentication methods as defined in RFC 1928 section 3. 18 | const ( 19 | MethodNoAuth AuthMethod = 0x00 20 | MethodUserPass AuthMethod = 0x02 21 | ) 22 | 23 | // Version is the protocol version as defined in RFC 1928 section 4. 24 | const Version = 0x05 25 | 26 | // Command is request commands as defined in RFC 1928 section 4. 27 | type Command uint8 28 | 29 | // SOCKS request commands as defined in RFC 1928 section 4. 30 | const ( 31 | CmdConnect Command = 0x01 32 | CmdBind Command = 0x02 33 | CmdUDPAssociate Command = 0x03 34 | ) 35 | 36 | func (c Command) String() string { 37 | switch c { 38 | case CmdConnect: 39 | return "CONNECT" 40 | case CmdBind: 41 | return "BIND" 42 | case CmdUDPAssociate: 43 | return "UDP ASSOCIATE" 44 | default: 45 | return "UNDEFINED" 46 | } 47 | } 48 | 49 | type Atyp = uint8 50 | 51 | // SOCKS address types as defined in RFC 1928 section 5. 52 | const ( 53 | AtypIPv4 Atyp = 0x01 54 | AtypDomainName Atyp = 0x03 55 | AtypIPv6 Atyp = 0x04 56 | ) 57 | 58 | // Reply field as defined in RFC 1928 section 6. 59 | type Reply uint8 60 | 61 | func (r Reply) String() string { 62 | switch r { 63 | case 0x00: 64 | return "succeeded" 65 | case 0x01: 66 | return "general SOCKS server failure" 67 | case 0x02: 68 | return "connection not allowed by ruleset" 69 | case 0x03: 70 | return "network unreachable" 71 | case 0x04: 72 | return "host unreachable" 73 | case 0x05: 74 | return "connection refused" 75 | case 0x06: 76 | return "TTL expired" 77 | case 0x07: 78 | return "command not supported" 79 | case 0x08: 80 | return "address type not supported" 81 | default: 82 | return fmt.Sprintf("unassigned <%#02x>", uint8(r)) 83 | } 84 | } 85 | 86 | // MaxAddrLen is the maximum size of SOCKS address in bytes. 87 | const MaxAddrLen = 1 + 1 + 255 + 2 88 | 89 | // MaxAuthLen is the maximum size of user/password field in SOCKS auth. 90 | const MaxAuthLen = 255 91 | 92 | // Addr represents a SOCKS address as defined in RFC 1928 section 5. 93 | type Addr []byte 94 | 95 | func (a Addr) Valid() bool { 96 | if len(a) < 1+1+2 /* minimum length */ { 97 | return false 98 | } 99 | 100 | switch a[0] { 101 | case AtypDomainName: 102 | if len(a) < 1+1+int(a[1])+2 { 103 | return false 104 | } 105 | case AtypIPv4: 106 | if len(a) < 1+net.IPv4len+2 { 107 | return false 108 | } 109 | case AtypIPv6: 110 | if len(a) < 1+net.IPv6len+2 { 111 | return false 112 | } 113 | } 114 | return true 115 | } 116 | 117 | // String returns string of socks5.Addr. 118 | func (a Addr) String() string { 119 | if !a.Valid() { 120 | return "" 121 | } 122 | 123 | var host, port string 124 | switch a[0] { 125 | case AtypDomainName: 126 | hostLen := int(a[1]) 127 | host = string(a[2 : 2+hostLen]) 128 | port = strconv.Itoa(int(binary.BigEndian.Uint16(a[2+hostLen:]))) 129 | case AtypIPv4: 130 | host = net.IP(a[1 : 1+net.IPv4len]).String() 131 | port = strconv.Itoa(int(binary.BigEndian.Uint16(a[1+net.IPv4len:]))) 132 | case AtypIPv6: 133 | host = net.IP(a[1 : 1+net.IPv6len]).String() 134 | port = strconv.Itoa(int(binary.BigEndian.Uint16(a[1+net.IPv6len:]))) 135 | } 136 | return net.JoinHostPort(host, port) 137 | } 138 | 139 | // UDPAddr converts a socks5.Addr to *net.UDPAddr. 140 | func (a Addr) UDPAddr() *net.UDPAddr { 141 | if !a.Valid() { 142 | return nil 143 | } 144 | 145 | var ip []byte 146 | var port int 147 | switch a[0] { 148 | case AtypDomainName /* unsupported */ : 149 | return nil 150 | case AtypIPv4: 151 | ip = make([]byte, net.IPv4len) 152 | copy(ip, a[1:1+net.IPv4len]) 153 | port = int(binary.BigEndian.Uint16(a[1+net.IPv4len:])) 154 | case AtypIPv6: 155 | ip = make([]byte, net.IPv6len) 156 | copy(ip, a[1:1+net.IPv6len]) 157 | port = int(binary.BigEndian.Uint16(a[1+net.IPv6len:])) 158 | } 159 | return &net.UDPAddr{IP: ip, Port: port} 160 | } 161 | 162 | // User provides basic socks5 auth functionality. 163 | type User struct { 164 | Username string 165 | Password string 166 | } 167 | 168 | // ClientHandshake fast-tracks SOCKS initialization to get target address to connect on client side. 169 | func ClientHandshake(rw io.ReadWriter, addr Addr, command Command, user *User) (Addr, error) { 170 | buf := make([]byte, MaxAddrLen) 171 | 172 | var method uint8 173 | if user != nil { 174 | method = MethodUserPass /* USERNAME/PASSWORD */ 175 | } else { 176 | method = MethodNoAuth /* NO AUTHENTICATION REQUIRED */ 177 | } 178 | 179 | // VER, NMETHODS, METHODS 180 | if _, err := rw.Write([]byte{Version, 0x01 /* NMETHODS */, method}); err != nil { 181 | return nil, err 182 | } 183 | 184 | // VER, METHOD 185 | if _, err := io.ReadFull(rw, buf[:2]); err != nil { 186 | return nil, err 187 | } 188 | 189 | if buf[0] != Version { 190 | return nil, errors.New("socks version mismatched") 191 | } 192 | 193 | if buf[1] == MethodUserPass /* USERNAME/PASSWORD */ { 194 | if user == nil { 195 | return nil, errors.New("auth required") 196 | } 197 | 198 | uLen := len(user.Username) 199 | pLen := len(user.Password) 200 | 201 | // Both ULEN and PLEN are limited to the range [1, 255]. 202 | if uLen == 0 || pLen == 0 { 203 | return nil, errors.New("auth username/password empty") 204 | } else if uLen > MaxAuthLen || pLen > MaxAuthLen { 205 | return nil, errors.New("auth username/password too long") 206 | } 207 | 208 | authMsgLen := 1 + 1 + uLen + 1 + pLen 209 | 210 | // password protocol version 211 | authMsg := bytes.NewBuffer(make([]byte, 0, authMsgLen)) 212 | authMsg.WriteByte(0x01 /* VER */) 213 | authMsg.WriteByte(byte(uLen) /* ULEN */) 214 | authMsg.WriteString(user.Username /* UNAME */) 215 | authMsg.WriteByte(byte(pLen) /* PLEN */) 216 | authMsg.WriteString(user.Password /* PASSWD */) 217 | 218 | if _, err := rw.Write(authMsg.Bytes()); err != nil { 219 | return nil, err 220 | } 221 | 222 | if _, err := io.ReadFull(rw, buf[:2]); err != nil { 223 | return nil, err 224 | } 225 | 226 | if buf[1] != 0x00 /* STATUS of SUCCESS */ { 227 | return nil, errors.New("rejected username/password") 228 | } 229 | 230 | } else if buf[1] != MethodNoAuth /* NO AUTHENTICATION REQUIRED */ { 231 | return nil, errors.New("unsupported method") 232 | } 233 | 234 | // VER, CMD, RSV, ADDR 235 | if _, err := rw.Write(bytes.Join([][]byte{{Version, byte(command), 0x00 /* RSV */}, addr}, nil)); err != nil { 236 | return nil, err 237 | } 238 | 239 | // VER, REP, RSV 240 | if _, err := io.ReadFull(rw, buf[:3]); err != nil { 241 | return nil, err 242 | } 243 | 244 | if rep := Reply(buf[1]); rep != 0x00 /* SUCCEEDED */ { 245 | return nil, fmt.Errorf("%s: %s", command, rep) 246 | } 247 | 248 | return ReadAddr(rw, buf) 249 | } 250 | 251 | func ReadAddr(r io.Reader, b []byte) (Addr, error) { 252 | if len(b) < MaxAddrLen { 253 | return nil, io.ErrShortBuffer 254 | } 255 | 256 | // read 1st byte for address type 257 | if _, err := io.ReadFull(r, b[:1]); err != nil { 258 | return nil, err 259 | } 260 | 261 | switch b[0] /* ATYP */ { 262 | case AtypDomainName: 263 | // read 2nd byte for domain length 264 | if _, err := io.ReadFull(r, b[1:2]); err != nil { 265 | return nil, err 266 | } 267 | domainLength := uint16(b[1]) 268 | _, err := io.ReadFull(r, b[2:2+domainLength+2]) 269 | return b[:1+1+domainLength+2], err 270 | case AtypIPv4: 271 | _, err := io.ReadFull(r, b[1:1+net.IPv4len+2]) 272 | return b[:1+net.IPv4len+2], err 273 | case AtypIPv6: 274 | _, err := io.ReadFull(r, b[1:1+net.IPv6len+2]) 275 | return b[:1+net.IPv6len+2], err 276 | default: 277 | return nil, errors.New("invalid address type") 278 | } 279 | } 280 | 281 | // SplitAddr slices a SOCKS address from beginning of b. Returns nil if failed. 282 | func SplitAddr(b []byte) Addr { 283 | addrLen := 1 284 | if len(b) < addrLen { 285 | return nil 286 | } 287 | 288 | switch b[0] { 289 | case AtypDomainName: 290 | if len(b) < 2 { 291 | return nil 292 | } 293 | addrLen = 1 + 1 + int(b[1]) + 2 294 | case AtypIPv4: 295 | addrLen = 1 + net.IPv4len + 2 296 | case AtypIPv6: 297 | addrLen = 1 + net.IPv6len + 2 298 | default: 299 | return nil 300 | } 301 | 302 | if len(b) < addrLen { 303 | return nil 304 | } 305 | 306 | return b[:addrLen] 307 | } 308 | 309 | // SerializeAddr serializes destination address and port to Addr. 310 | // If a domain name is provided, AtypDomainName would be used first. 311 | func SerializeAddr(domainName string, dstIP net.IP, dstPort uint16) Addr { 312 | var ( 313 | buf [][]byte 314 | port [2]byte 315 | ) 316 | binary.BigEndian.PutUint16(port[:], dstPort) 317 | 318 | if domainName != "" /* Domain Name */ { 319 | length := len(domainName) 320 | buf = [][]byte{{AtypDomainName, uint8(length)}, []byte(domainName), port[:]} 321 | } else if dstIP.To4() != nil /* IPv4 */ { 322 | buf = [][]byte{{AtypIPv4}, dstIP.To4(), port[:]} 323 | } else /* IPv6 */ { 324 | buf = [][]byte{{AtypIPv6}, dstIP.To16(), port[:]} 325 | } 326 | return bytes.Join(buf, nil) 327 | } 328 | 329 | // ParseAddr parses a socks addr from net.Addr. 330 | // This is a fast path of ParseAddrString(addr.String()) 331 | func ParseAddr(addr net.Addr) Addr { 332 | switch v := addr.(type) { 333 | case *net.TCPAddr: 334 | return SerializeAddr("", v.IP, uint16(v.Port)) 335 | case *net.UDPAddr: 336 | return SerializeAddr("", v.IP, uint16(v.Port)) 337 | default: 338 | return ParseAddrString(addr.String()) 339 | } 340 | } 341 | 342 | // ParseAddrString parses the address in string s to Addr. Returns nil if failed. 343 | func ParseAddrString(s string) Addr { 344 | host, port, err := net.SplitHostPort(s) 345 | if err != nil { 346 | return nil 347 | } 348 | 349 | dstPort, err := strconv.ParseUint(port, 10, 16) 350 | if err != nil { 351 | return nil 352 | } 353 | 354 | if ip := net.ParseIP(host); ip != nil { 355 | return SerializeAddr("", ip, uint16(dstPort)) 356 | } 357 | return SerializeAddr(host, nil, uint16(dstPort)) 358 | } 359 | 360 | // DecodeUDPPacket split `packet` to addr payload, and this function is mutable with `packet` 361 | func DecodeUDPPacket(packet []byte) (addr Addr, payload []byte, err error) { 362 | if len(packet) < 5 { 363 | err = errors.New("insufficient length of packet") 364 | return 365 | } 366 | 367 | // packet[0] and packet[1] are reserved 368 | if !bytes.Equal(packet[:2], []byte{0x00, 0x00}) { 369 | err = errors.New("reserved fields should be zero") 370 | return 371 | } 372 | 373 | // The FRAG field indicates whether or not this datagram is one of a 374 | // number of fragments. If implemented, the high-order bit indicates 375 | // end-of-fragment sequence, while a value of X'00' indicates that this 376 | // datagram is standalone. Values between 1 and 127 indicate the 377 | // fragment position within a fragment sequence. Each receiver will 378 | // have a REASSEMBLY QUEUE and a REASSEMBLY TIMER associated with these 379 | // fragments. The reassembly queue must be reinitialized and the 380 | // associated fragments abandoned whenever the REASSEMBLY TIMER expires, 381 | // or a new datagram arrives carrying a FRAG field whose value is less 382 | // than the highest FRAG value processed for this fragment sequence. 383 | // The reassembly timer MUST be no less than 5 seconds. It is 384 | // recommended that fragmentation be avoided by applications wherever 385 | // possible. 386 | // 387 | // Ref: https://datatracker.ietf.org/doc/html/rfc1928#section-7 388 | if packet[2] != 0x00 /* fragments */ { 389 | err = errors.New("discarding fragmented payload") 390 | return 391 | } 392 | 393 | addr = SplitAddr(packet[3:]) 394 | if addr == nil { 395 | err = errors.New("socks5 UDP addr is nil") 396 | } 397 | 398 | payload = packet[3+len(addr):] 399 | return 400 | } 401 | 402 | func EncodeUDPPacket(addr Addr, payload []byte) (packet []byte, err error) { 403 | if addr == nil { 404 | return nil, errors.New("address is invalid") 405 | } 406 | packet = bytes.Join([][]byte{{0x00, 0x00, 0x00}, addr, payload}, nil) 407 | return 408 | } 409 | -------------------------------------------------------------------------------- /transport/socks5_test.go: -------------------------------------------------------------------------------- 1 | package socks5 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestSocks5ClientHandshake(t *testing.T) { 12 | // Mock server responses 13 | readBuffer := &bytes.Buffer{} 14 | readBuffer.Write([]byte{Version, MethodUserPass}) 15 | readBuffer.Write([]byte{Version, 0x00 /* STATUS of SUCCESS */}) 16 | readBuffer.Write([]byte{Version, 0x00 /* STATUS of SUCCESS */, 0x00 /* RSV */}) 17 | readBuffer.Write([]byte{AtypIPv4, 0x1, 0x2, 0x3, 0x4, 0x0, 0x0 /* IPv4: 1.2.3.4:0 */}) 18 | reader := bufio.NewReader(bytes.NewReader(readBuffer.Bytes())) 19 | 20 | writeBuffer := &bytes.Buffer{} 21 | writer := bufio.NewWriter(writeBuffer) 22 | 23 | io := bufio.NewReadWriter(reader, writer) 24 | 25 | addr, err := ClientHandshake(io, []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, CmdConnect, &User{ 26 | Username: "test", 27 | Password: "6ab49d8b-a009-44e4-bd53-fbdb48fbe7eb", 28 | }) 29 | 30 | assert.Nil(t, err, "Failed to perform SOCKS5 client handshake: %v", err) 31 | assert.Equal(t, "1.2.3.4:0", addr.String(), "Incorrect address obtained from SOCKS5 client handshake") 32 | } 33 | -------------------------------------------------------------------------------- /tunnel/addr.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import ( 4 | "net" 5 | "strconv" 6 | ) 7 | 8 | // parseAddr parses net.Addr to IP and port. 9 | func parseAddr(addr net.Addr) (net.IP, uint16) { 10 | switch v := addr.(type) { 11 | case *net.TCPAddr: 12 | return v.IP, uint16(v.Port) 13 | case *net.UDPAddr: 14 | return v.IP, uint16(v.Port) 15 | case nil: 16 | return nil, 0 17 | default: 18 | return parseAddrString(addr.String()) 19 | } 20 | } 21 | 22 | // parseAddrString parses address string to IP and port. 23 | func parseAddrString(addr string) (net.IP, uint16) { 24 | host, port, _ := net.SplitHostPort(addr) 25 | portInt, _ := strconv.ParseUint(port, 10, 16) 26 | return net.ParseIP(host), uint16(portInt) 27 | } 28 | -------------------------------------------------------------------------------- /tunnel/tcp.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import ( 4 | "github.com/DaniilSokolyuk/go-pcap2socks/common/pool" 5 | "github.com/DaniilSokolyuk/go-pcap2socks/core/adapter" 6 | "gvisor.dev/gvisor/pkg/log" 7 | "io" 8 | "log/slog" 9 | "net" 10 | "sync" 11 | "time" 12 | 13 | M "github.com/DaniilSokolyuk/go-pcap2socks/md" 14 | "github.com/DaniilSokolyuk/go-pcap2socks/proxy" 15 | ) 16 | 17 | const ( 18 | // tcpWaitTimeout implements a TCP half-close timeout. 19 | tcpWaitTimeout = 60 * time.Second 20 | ) 21 | 22 | func handleTCPConn(originConn adapter.TCPConn) { 23 | defer originConn.Close() 24 | 25 | id := originConn.ID() 26 | metadata := &M.Metadata{ 27 | Network: M.TCP, 28 | SrcIP: net.IP(id.RemoteAddress.AsSlice()), 29 | SrcPort: id.RemotePort, 30 | DstIP: net.IP(id.LocalAddress.AsSlice()), 31 | DstPort: id.LocalPort, 32 | } 33 | 34 | remoteConn, err := proxy.Dial(metadata) 35 | if err != nil { 36 | slog.Warn("[TCP] Dial error", "dest", metadata.DestinationAddress(), "error", err) 37 | return 38 | } 39 | metadata.MidIP, metadata.MidPort = parseAddr(remoteConn.LocalAddr()) 40 | 41 | defer remoteConn.Close() 42 | 43 | slog.Info("[TCP] Connection", "source", metadata.SourceAddress(), "dest", metadata.DestinationAddress()) 44 | pipe(originConn, remoteConn) 45 | } 46 | 47 | // pipe copies copy data to & from provided net.Conn(s) bidirectionally. 48 | func pipe(origin, remote net.Conn) { 49 | wg := sync.WaitGroup{} 50 | wg.Add(2) 51 | 52 | go unidirectionalStream(remote, origin, "origin->remote", &wg) 53 | go unidirectionalStream(origin, remote, "remote->origin", &wg) 54 | 55 | wg.Wait() 56 | } 57 | 58 | func unidirectionalStream(dst, src net.Conn, dir string, wg *sync.WaitGroup) { 59 | defer wg.Done() 60 | buf := pool.Get(pool.RelayBufferSize) 61 | if _, err := io.CopyBuffer(dst, src, buf); err != nil { 62 | log.Debugf("[TCP] copy data for %s: %v", dir, err) 63 | } 64 | pool.Put(buf) 65 | // Do the upload/download side TCP half-close. 66 | if cr, ok := src.(interface{ CloseRead() error }); ok { 67 | cr.CloseRead() 68 | } 69 | if cw, ok := dst.(interface{ CloseWrite() error }); ok { 70 | cw.CloseWrite() 71 | } 72 | // Set TCP half-close timeout. 73 | dst.SetReadDeadline(time.Now().Add(tcpWaitTimeout)) 74 | } 75 | -------------------------------------------------------------------------------- /tunnel/tunnel.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import ( 4 | "github.com/DaniilSokolyuk/go-pcap2socks/core/adapter" 5 | ) 6 | 7 | // Unbuffered TCP/UDP queues. 8 | var ( 9 | _tcpQueue = make(chan adapter.TCPConn) 10 | ) 11 | 12 | func init() { 13 | go process() 14 | } 15 | 16 | // TCPIn return fan-in TCP queue. 17 | func TCPIn() chan<- adapter.TCPConn { 18 | return _tcpQueue 19 | } 20 | 21 | func process() { 22 | for { 23 | select { 24 | case conn := <-_tcpQueue: 25 | go handleTCPConn(conn) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tunnel/udp.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import ( 4 | "errors" 5 | "github.com/DaniilSokolyuk/go-pcap2socks/common/pool" 6 | "github.com/DaniilSokolyuk/go-pcap2socks/core/adapter" 7 | "github.com/DaniilSokolyuk/go-pcap2socks/proxy" 8 | "io" 9 | "log/slog" 10 | "net" 11 | "os" 12 | "sync" 13 | "time" 14 | ) 15 | 16 | var UdpSessionTimeout = 5 * time.Minute 17 | 18 | func HandleUDPConn(uc adapter.UDPConn) { 19 | metadata := uc.MD() 20 | 21 | pc, err := proxy.DialUDP(metadata) 22 | if err != nil { 23 | slog.Warn("[UDP] dial error: ", "error", err) 24 | return 25 | } 26 | defer pc.Close() 27 | 28 | slog.Info("[UDP] Connection", "source", metadata.SourceAddress(), "dest", metadata.DestinationAddress()) 29 | 30 | wg := sync.WaitGroup{} 31 | wg.Add(2) 32 | 33 | go pipeChannel(pc, uc, &wg) 34 | go pipeChannel(uc, pc, &wg) 35 | wg.Wait() 36 | 37 | uc.Close() 38 | slog.Info("[UDP] Connection closed", "source", metadata.SourceAddress(), "dest", metadata.DestinationAddress()) 39 | } 40 | 41 | func pipeChannel(from net.PacketConn, to net.PacketConn, wg *sync.WaitGroup) { 42 | defer wg.Done() 43 | 44 | buf := pool.Get(pool.MaxSegmentSize) 45 | defer pool.Put(buf) 46 | 47 | for { 48 | from.SetReadDeadline(time.Now().Add(UdpSessionTimeout)) 49 | n, dest, err := from.ReadFrom(buf) 50 | if err != nil { 51 | if errors.Is(err, io.ErrClosedPipe) { 52 | slog.Warn("[UDP] pipe closed", "source", from.LocalAddr(), "dest", to.LocalAddr(), "error", err) 53 | return 54 | } 55 | if !errors.Is(err, os.ErrDeadlineExceeded) { 56 | slog.Warn("[UDP] read error", "source", from.LocalAddr(), "dest", to.LocalAddr(), "error", err) 57 | } 58 | 59 | return 60 | } 61 | 62 | to.SetWriteDeadline(time.Now().Add(UdpSessionTimeout)) 63 | if _, err := to.WriteTo(buf[:n], dest); err != nil { 64 | slog.Warn("[UDP] write error", "source", from.LocalAddr(), "dest", dest, "error", err) 65 | return 66 | } 67 | } 68 | } 69 | --------------------------------------------------------------------------------