├── .github └── workflows │ └── ci.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── README.en.md ├── README.md ├── VERSION ├── cmd ├── main.go └── run.go ├── config ├── config.go └── config_test.go ├── db ├── bolt.go ├── db.go └── dolt_test.go ├── go.mod ├── go.sum ├── gus ├── gus.go └── gus_test.go ├── img ├── gus-curl.png └── gus-run.png ├── pkg ├── go-socks4 │ ├── LICENSE │ ├── README.md │ ├── socks4.go │ └── socks4_test.go └── ss │ └── ss.go ├── proxy ├── hosts.go ├── proxy.go ├── proxy_test.go └── types.go ├── proxyhosts.txt ├── test └── ip │ ├── main.go │ └── runtime.png ├── tools ├── scripts │ └── proxy_spider.sh └── thief │ └── hidemyna.me │ └── main.go └── utils ├── dig.go ├── hash.go ├── ip.go ├── ping.go ├── ua.go └── utils_test.go /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a golang project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go 3 | 4 | name: CI 5 | 6 | on: 7 | push: 8 | branches: [ "*" ] 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - name: Set up Go 16 | uses: actions/setup-go@v4 17 | with: 18 | go-version: '1.20' 19 | - name: Build 20 | run: make build 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # binaries 11 | bin/* 12 | 13 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 14 | .glide/ 15 | vendor/ 16 | 17 | # local test 18 | proxyhosts_test.txt 19 | gus-proxy.db 20 | ssr.txt -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine 2 | RUN [ ! -e /etc/nsswitch.conf ] && echo 'hosts: files dns' > /etc/nsswitch.conf 3 | COPY bin/gus-proxy /usr/bin 4 | EXPOSE 8080 5 | CMD [ "gus-proxy", "-f", "/data/proxies.txt", "--db-path", "/data/gus.db" ] -------------------------------------------------------------------------------- /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 | {one line to give the program's name and a brief idea of what it does.} 635 | Copyright (C) {year} {name of author} 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 | {project} Copyright (C) {year} {fullname} 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NAME = gus-proxy 2 | PKG = github.com/wrfly/$(NAME)/cmd 3 | BIN = bin 4 | IMAGE := wrfly/$(NAME) 5 | 6 | VERSION := $(shell cat VERSION) 7 | COMMITID := $(shell git rev-parse --short HEAD) 8 | BUILDAT := $(shell date +%Y-%m-%d) 9 | 10 | CTIMEVAR = -X main.CommitID=$(COMMITID) \ 11 | -X main.Version=$(VERSION) \ 12 | -X main.BuildAt=$(BUILDAT) 13 | GO_LDFLAGS = -ldflags "-s -w $(CTIMEVAR)" -tags netgo 14 | 15 | export GO111MODULE=on 16 | 17 | .PHONY: bin 18 | bin: 19 | mkdir -p bin 20 | 21 | .PHONY: build 22 | build: bin 23 | go build $(GO_LDFLAGS) -o $(BIN)/$(NAME) $(PKG) 24 | 25 | .PHONY: test 26 | test: 27 | go test -cover -v ./... 28 | 29 | .PHONY: dev 30 | dev: build 31 | ./$(BIN)/$(NAME) -f proxyhosts_test.txt -d 32 | 33 | .PHONY: curl 34 | curl: 35 | for i in `seq 9`;do \ 36 | curl --proxy http://localhost:8080 i.kfd.me ; \ 37 | done 38 | 39 | .PHONY: release 40 | release: 41 | GOOS=linux GOARCH=amd64 go build $(GO_LDFLAGS) -o $(BIN)/$(NAME)_linux_amd64 . 42 | GOOS=darwin GOARCH=amd64 go build $(GO_LDFLAGS) -o $(BIN)/$(NAME)_darwin_amd64 . 43 | tar -C $(BIN) -czf $(BIN)/$(NAME)_linux_amd64.tgz $(NAME)_linux_amd64 44 | tar -C $(BIN) -czf $(BIN)/$(NAME)_darwin_amd64.tgz $(NAME)_darwin_amd64 45 | 46 | .PHONY: image 47 | image: 48 | docker build -t $(IMAGE) . 49 | 50 | .PHONY: push-image 51 | push-image: 52 | docker push $(IMAGE) 53 | 54 | .PHONY: push-develop 55 | push-develop: 56 | docker tag $(IMAGE) $(IMAGE):develop 57 | docker push $(IMAGE):develop 58 | 59 | .PHONY: push-tag 60 | push-tag: 61 | docker tag $(IMAGE) $(IMAGE):$(VERSION) 62 | docker push $(IMAGE):$(VERSION) 63 | -------------------------------------------------------------------------------- /README.en.md: -------------------------------------------------------------------------------- 1 | # Gus-Proxy 2 | 3 | "gus - the heavy-duty drug trafficker in *Breaking Bad*" 4 | 5 | [![Build Status](https://travis-ci.org/wrfly/gus-proxy.svg?branch=master)](https://travis-ci.org/wrfly/gus-proxy) 6 | [![Go Report Card](https://goreportcard.com/badge/github.com/wrfly/gus-proxy)](https://goreportcard.com/report/github.com/wrfly/gus-proxy) 7 | 8 | [README.Chinese](README.md) 9 | 10 | --- 11 | 12 | ## Run 13 | 14 | ```bash 15 | # prepare the proxies 16 | mkdir -p data 17 | touch data/proxies.txt 18 | # put your proxies in data/proxies.txt 19 | # the format could be: 20 | # socks5://127.0.0.1:1080 21 | # http://user:pass@127.0.0.1:1081 22 | # socks4://127.0.0.1:1082 23 | # direct://0.0.0.0 24 | # ss://AEAD_CHACHA20_POLY1305:passw0rd@127.0.0.1:1083 25 | 26 | # then: 27 | sudo docker run --rm -ti --name gus-proxy \ 28 | -p 8080:8080 \ 29 | -v `pwd`/data:/data \ 30 | wrfly/gus-proxy 31 | ``` 32 | 33 | ## Thoughts 34 | 35 | > Change our IP address every request 36 | 37 | 1. Chose a different proxy in our proxy poll every request 38 | 1. If our IP changed, the server side may not auth us because of the session-IP pair 39 | 1. No use for session authentication 40 | 1. The aim for this tool is to resolve the restrict of IP request limit 41 | 42 | ## Design 43 | 44 | 1. An top layer HTTP-proxy 45 | 1. The program load a proxy list(HTTP or Socks5) during start 46 | 1. Chose a proxy every request 47 | 1. May have different choose algorithm: round-robin|random|ping 48 | 1. Verify the availability of the proxy 49 | 1. Change our UA every request(it's an option) 50 | 1. Lookup target's all IP address, replace target host per request 51 | 52 | ## Show off 53 | 54 | ![Gus-Running](img/gus-run.png) 55 | ![Curl-test](img/gus-curl.png) 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gus-Proxy 2 | 3 | "gus - 绝命毒师里的大毒枭" 4 | 5 | [![Build Status](https://travis-ci.org/wrfly/gus-proxy.svg?branch=master)](https://travis-ci.org/wrfly/gus-proxy) 6 | [![Go Report Card](https://goreportcard.com/badge/github.com/wrfly/gus-proxy)](https://goreportcard.com/report/github.com/wrfly/gus-proxy) 7 | 8 | [README.English](README.en.md) 9 | 10 | --- 11 | 12 | ## 运行 13 | 14 | ```bash 15 | # prepare the proxies 16 | mkdir -p data 17 | touch data/proxies.txt 18 | # put your proxies in data/proxies.txt 19 | # the format could be: 20 | # socks5://127.0.0.1:1080 21 | # http://user:pass@127.0.0.1:1081 22 | # socks4://127.0.0.1:1082 23 | # direct://0.0.0.0 24 | # ss://AEAD_CHACHA20_POLY1305:passw0rd@127.0.0.1:1083 25 | 26 | # then: 27 | sudo docker run --rm -ti --name gus-proxy \ 28 | -p 8080:8080 \ 29 | -v `pwd`/data:/data \ 30 | wrfly/gus-proxy 31 | ``` 32 | 33 | ## 思路 34 | 35 | > 打一枪换一个地方 36 | 37 | 1. 每次请求都从代理池中选取一个代理 38 | 1. 但是这样会不会触发server端的验证,即session与IP匹配 39 | 1. 但是如果server端有这种IP验证的话,就没必要用这东西了 40 | 1. 要解决的是server限制某一IP访问频率的问题 41 | 42 | 没问题。 43 | 44 | ## 设计 45 | 46 | 1. 程序对上层表现为一个HTTP代理 47 | 1. 程序加载一个代理列表(HTTP/Socks5) [或者默认配置一个代理列表] 48 | 1. 每次的请求都从代理列表中选取一个 49 | 1. 选取的算法可能是轮询、随机、或其他目前没想到的 50 | 1. 要验证proxy的可用性 51 | 1. 每次请求替换UA 52 | 1. 请求资源的时候,查询目标资源地址全部的IP,随机 53 | 54 | ## 效果 55 | 56 | ![Gus-Running](img/gus-run.png) 57 | ![Curl-test](img/gus-curl.png) 58 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.2.0 -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | _ "net/http/pprof" 7 | "os" 8 | "sort" 9 | 10 | "github.com/sirupsen/logrus" 11 | "github.com/urfave/cli/v2" 12 | 13 | "github.com/wrfly/gus-proxy/config" 14 | ) 15 | 16 | const helpTemplate = `NAME: 17 | {{.Name}} - {{.Usage}} 18 | {{if len .Authors}} 19 | AUTHOR: 20 | {{range .Authors}}{{ . }}{{end}} 21 | {{end}}{{if .Version}} 22 | VERSION: 23 | {{.Version}} 24 | {{end}}{{if .Commands}} 25 | OPTIONS: 26 | {{range .VisibleFlags}} {{.}} 27 | {{end}}{{end}}` 28 | 29 | // build info 30 | var ( 31 | Version string 32 | CommitID string 33 | BuildAt string 34 | ) 35 | 36 | func main() { 37 | conf := &config.Config{ 38 | NoProxyCIDR: make([]*net.IPNet, 0), 39 | } 40 | 41 | app := cli.App{ 42 | Name: "gus-proxy", 43 | Usage: "Change proxy for every request", 44 | Version: fmt.Sprintf("version: %s\tcommit: %s\tdate: %s", 45 | Version, CommitID, BuildAt), 46 | Authors: []*cli.Author{{ 47 | Name: "wrfly", 48 | Email: "mr.wrfly@gmail.com", 49 | }}, 50 | Flags: []cli.Flag{ 51 | &cli.StringFlag{ 52 | Name: "file", 53 | Aliases: []string{"f"}, 54 | Value: "proxyhosts.txt", 55 | Usage: "proxy file path, filepath or URL", 56 | Destination: &conf.ProxyFilePath, 57 | }, 58 | &cli.StringSliceFlag{ 59 | Name: "no-proxy-cidr", 60 | Aliases: []string{"np"}, 61 | Value: cli.NewStringSlice("127.0.0.0/32"), 62 | Usage: "no proxy CIDR list", 63 | }, 64 | &cli.StringFlag{ 65 | Name: "db-path", 66 | Value: "gus-proxy.db", 67 | Usage: "bolt db storage", 68 | Destination: &conf.DBFilePath, 69 | }, 70 | &cli.BoolFlag{ 71 | Name: "debug", 72 | Aliases: []string{"d"}, 73 | Usage: "debug mode", 74 | Value: false, 75 | Destination: &conf.Debug, 76 | }, 77 | &cli.StringFlag{ 78 | Name: "scheduler", 79 | Aliases: []string{"s"}, 80 | Value: "round_robin", 81 | Usage: "scheduler: round_robin|ping|random", 82 | Destination: &conf.Scheduler, 83 | }, 84 | &cli.StringFlag{ 85 | Name: "listen", 86 | Aliases: []string{"l"}, 87 | Value: "8080", 88 | Usage: "port to bind", 89 | Destination: &conf.ListenPort, 90 | }, 91 | &cli.StringFlag{ 92 | Name: "debug-port", 93 | Value: "8081", 94 | Usage: "port for pprof debug", 95 | Destination: &conf.DebugPort, 96 | }, 97 | &cli.BoolFlag{ 98 | Name: "random-ua", 99 | Aliases: []string{"ru"}, 100 | Usage: "enable random UA", 101 | Destination: &conf.RandomUA, 102 | }, 103 | &cli.IntFlag{ 104 | Name: "update", 105 | Value: 30, 106 | Usage: "proxies update interval(second)", 107 | Destination: &conf.ProxyUpdateInterval, 108 | }, 109 | }, 110 | CustomAppHelpTemplate: helpTemplate, 111 | Action: func(c *cli.Context) error { 112 | logrus.SetLevel(logrus.DebugLevel) 113 | 114 | for _, cidr := range c.StringSlice("no-proxy-cidr") { 115 | _, n, err := net.ParseCIDR(cidr) 116 | if err != nil { 117 | logrus.Fatalf("invalid CIDR: %s, error: %s", cidr, err) 118 | } 119 | logrus.Debugf("append CIDR %s", n.String()) 120 | conf.NoProxyCIDR = append(conf.NoProxyCIDR, n) 121 | } 122 | logrus.SetLevel(logrus.InfoLevel) 123 | 124 | return runGus(conf) 125 | }, 126 | } 127 | 128 | sort.Sort(cli.FlagsByName(app.Flags)) 129 | 130 | app.Run(os.Args) 131 | } 132 | -------------------------------------------------------------------------------- /cmd/run.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "os" 8 | "os/signal" 9 | "sync" 10 | "syscall" 11 | "time" 12 | 13 | "github.com/sirupsen/logrus" 14 | 15 | "github.com/wrfly/gus-proxy/config" 16 | "github.com/wrfly/gus-proxy/db" 17 | "github.com/wrfly/gus-proxy/gus" 18 | ) 19 | 20 | func runGus(conf *config.Config) error { 21 | if conf.Debug { 22 | logrus.SetLevel(logrus.DebugLevel) 23 | } 24 | logrus.Info("starting gus-proxy") 25 | 26 | if err := conf.Validate(); err != nil { 27 | logrus.Fatalf("bad config error: %s", err) 28 | } 29 | 30 | ctx, cancel := context.WithCancel(context.Background()) 31 | defer cancel() 32 | var wg sync.WaitGroup 33 | 34 | // update proxy status 35 | readyChan := make(chan interface{}) 36 | wg.Add(1) 37 | go func() { 38 | defer wg.Done() 39 | logrus.Info("updating proxies") 40 | conf.UpdateProxies() 41 | readyChan <- true 42 | close(readyChan) 43 | 44 | tk := time.NewTicker(time.Second * time.Duration(conf.ProxyUpdateInterval)) 45 | defer tk.Stop() 46 | for { 47 | select { 48 | case <-ctx.Done(): 49 | return 50 | case <-tk.C: 51 | conf.UpdateProxies() 52 | } 53 | } 54 | }() 55 | <-readyChan 56 | 57 | // handle signals 58 | logrus.Debug("handle sigs") 59 | sigStop := make(chan os.Signal) 60 | signal.Notify(sigStop, syscall.SIGINT, syscall.SIGTERM) 61 | sigKill := make(chan os.Signal) 62 | signal.Notify(sigKill, os.Kill) 63 | 64 | // init db 65 | logrus.Debug("init dns db") 66 | dnsDB, err := db.New(conf.DBFilePath) 67 | if err != nil { 68 | logrus.Fatal(err) 69 | } 70 | defer dnsDB.Close() 71 | 72 | go func() { 73 | if !conf.Debug { 74 | return 75 | } 76 | addr := fmt.Sprintf(":%s", conf.DebugPort) 77 | logrus.Infof("debug is serving on %s", addr) 78 | http.ListenAndServe(addr, nil) 79 | }() 80 | 81 | srv := http.Server{ 82 | Addr: fmt.Sprintf("0.0.0.0:%s", conf.ListenPort), 83 | Handler: gus.New(conf, dnsDB), 84 | } 85 | go func() { 86 | wg.Add(1) 87 | defer wg.Done() 88 | logrus.Infof("serving on %s", srv.Addr) 89 | if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { 90 | logrus.Error(err) 91 | } 92 | }() 93 | 94 | select { 95 | case <-sigStop: 96 | logrus.Info("about to stop") 97 | cancel() 98 | srvCtx, srvCancel := context.WithTimeout(context.Background(), time.Second*3) 99 | defer srvCancel() 100 | srv.Shutdown(srvCtx) 101 | quit := make(chan struct{}) 102 | go func() { 103 | wg.Wait() 104 | quit <- struct{}{} 105 | }() 106 | defer close(quit) 107 | 108 | select { 109 | case <-sigStop: 110 | srvCancel() 111 | logrus.Warn("force quit!") 112 | case <-quit: 113 | logrus.Info("quit") 114 | } 115 | case <-sigKill: 116 | cancel() 117 | srv.Close() 118 | } 119 | 120 | logrus.Info("gus-proxy stopped") 121 | return nil 122 | } 123 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "net" 8 | "net/http" 9 | "net/url" 10 | "os" 11 | "strings" 12 | "sync" 13 | "sync/atomic" 14 | 15 | "github.com/sirupsen/logrus" 16 | 17 | "github.com/wrfly/gus-proxy/proxy" 18 | "github.com/wrfly/gus-proxy/utils" 19 | ) 20 | 21 | // Config ... 22 | type Config struct { 23 | Debug bool 24 | ProxyFilePath string 25 | NoProxyCIDR []*net.IPNet 26 | Scheduler string 27 | ListenPort string 28 | DebugPort string 29 | RandomUA bool 30 | ProxyUpdateInterval int 31 | DBFilePath string 32 | 33 | proxyHostsHash string 34 | proxyAliveHash string 35 | proxyFilePathIsURL bool 36 | proxyHosts proxy.Hosts 37 | oldHosts []string 38 | availableProxyHosts proxy.Hosts 39 | 40 | m sync.RWMutex 41 | } 42 | 43 | // Validate the config 44 | func (c *Config) Validate() error { 45 | logrus.Debugf("get proxyfile [%s]", c.ProxyFilePath) 46 | _, err := os.Open(c.ProxyFilePath) 47 | if err != nil && os.IsNotExist(err) { 48 | resp, err := http.DefaultClient.Get(c.ProxyFilePath) 49 | if err != nil { 50 | return fmt.Errorf("get host %s error: %s", c.ProxyFilePath, err) 51 | } 52 | defer resp.Body.Close() 53 | if resp.StatusCode != http.StatusOK { 54 | return fmt.Errorf("Hostfile [%s] not found", c.ProxyFilePath) 55 | } 56 | c.proxyFilePathIsURL = true 57 | } 58 | 59 | switch c.Scheduler { 60 | case proxy.RR: 61 | case proxy.RANDOM: 62 | case proxy.PING: 63 | default: 64 | return fmt.Errorf("Unknown scheduler: %s", c.Scheduler) 65 | } 66 | 67 | // listen port 68 | l, err := net.Listen("tcp4", fmt.Sprintf("0.0.0.0:%s", c.ListenPort)) 69 | if err != nil { 70 | return fmt.Errorf("Bind port error: %s", err) 71 | } 72 | l.Close() 73 | 74 | logrus.Debug("validate ok") 75 | return nil 76 | } 77 | 78 | // LoadHosts returns the proxy hosts 79 | func (c *Config) loadHosts() error { 80 | var ( 81 | proxyfile io.ReadCloser 82 | proxyHosts proxy.Hosts 83 | newHosts []string 84 | err error 85 | ) 86 | 87 | if c.proxyFilePathIsURL { 88 | resp, err := http.DefaultClient.Get(c.ProxyFilePath) 89 | if err != nil { 90 | return fmt.Errorf("get host %s error: %s", c.ProxyFilePath, err) 91 | } 92 | proxyfile = resp.Body 93 | } else { 94 | proxyfile, err = os.Open(c.ProxyFilePath) 95 | if err != nil { 96 | return err 97 | } 98 | } 99 | defer proxyfile.Close() 100 | s := bufio.NewScanner(proxyfile) 101 | var l int 102 | for s.Scan() { 103 | l++ 104 | target := s.Text() 105 | target = strings.ToValidUTF8(target, "") 106 | target = strings.TrimFunc(target, func(r rune) bool { 107 | if r == '\r' || r == '\n' { 108 | return true 109 | } 110 | return false 111 | }) 112 | if err := s.Err(); err != nil { 113 | return fmt.Errorf("read hosts error: %s", err) 114 | } 115 | if target == "" || strings.HasPrefix(target, "#") { 116 | // skip empty line and comments 117 | continue 118 | } 119 | 120 | // verify hosts 121 | logrus.Debugf("validate proxy format: %s", target) 122 | proxyline, err := url.Parse(target) 123 | if err != nil { 124 | logrus.Error(err) 125 | continue 126 | } 127 | if !proxyline.IsAbs() { 128 | logrus.Errorf("bad proxy: %s, not absolute", target) 129 | continue 130 | } 131 | 132 | newHosts = append(newHosts, target) 133 | } 134 | if c.proxyHostsHash == utils.HashSlice(newHosts) { 135 | return nil 136 | } 137 | c.proxyHostsHash = utils.HashSlice(newHosts) 138 | 139 | logrus.Infof("loading %d proxies", len(newHosts)) 140 | 141 | c.m.RLock() 142 | oldHostsMap := make(map[string]bool, len(newHosts)) 143 | for _, host := range c.oldHosts { 144 | oldHostsMap[host] = true 145 | } 146 | var ( 147 | newProxyWG sync.WaitGroup 148 | limit = make(chan struct{}, 200) 149 | badProxy uint32 150 | ) 151 | for i, host := range newHosts { 152 | if oldHostsMap[host] { 153 | if p := c.proxyHosts.Host(i); p != nil { 154 | proxyHosts.Add(p) 155 | } 156 | } else { 157 | newProxyWG.Add(1) 158 | limit <- struct{}{} 159 | go func(host string) { 160 | defer func() { 161 | newProxyWG.Done() 162 | <-limit 163 | }() 164 | 165 | proxyhost := &proxy.Host{Addr: host} 166 | if err := proxyhost.Init(); err != nil { 167 | logrus.Error(err) 168 | atomic.AddUint32(&badProxy, 1) 169 | } else { 170 | proxyHosts.Add(proxyhost) 171 | } 172 | }(host) 173 | } 174 | } 175 | newProxyWG.Wait() 176 | 177 | c.m.RUnlock() 178 | 179 | c.m.Lock() 180 | logrus.Warnf("load %d dead proxies", badProxy) 181 | logrus.Infof("load %d alive proxies", proxyHosts.Len()) 182 | c.proxyHosts = proxyHosts 183 | c.m.Unlock() 184 | 185 | return nil 186 | } 187 | 188 | // UpdateProxies update proxy's attr 189 | func (c *Config) UpdateProxies() { 190 | err := c.loadHosts() 191 | if err != nil { 192 | logrus.Errorf("load proxy error: %s", err) 193 | return 194 | } 195 | 196 | var ( 197 | wg sync.WaitGroup 198 | availableProxy int32 199 | ) 200 | 201 | limit := make(chan struct{}, 1e3) 202 | for _, host := range c.proxyHosts.Hosts() { 203 | limit <- struct{}{} 204 | wg.Add(1) 205 | go func(host *proxy.Host) { 206 | defer wg.Done() 207 | if err := host.CheckAvaliable(); err != nil { 208 | host.Available = false 209 | } else { 210 | atomic.AddInt32(&availableProxy, 1) 211 | } 212 | logrus.Debugf("proxy: %s, Available: %t", 213 | host.Addr, host.Available) 214 | <-limit 215 | }(host) 216 | } 217 | wg.Wait() 218 | 219 | totalNum := c.proxyHosts.Len() 220 | // mast in this order (small to big) 221 | switch { 222 | case availableProxy*4 <= totalNum: 223 | logrus.Errorf("not enough available proxies, available: [%d] total: [%d]", 224 | availableProxy, totalNum) 225 | case availableProxy*2 <= totalNum: 226 | logrus.Warnf("half of the proxies was down, available: [%d] total: [%d]", 227 | availableProxy, totalNum) 228 | } 229 | 230 | oldHosts := make([]string, 0, c.proxyHosts.Len()) 231 | for _, host := range c.proxyHosts.Hosts() { 232 | if host.Available { 233 | oldHosts = append(oldHosts, host.Addr) 234 | } 235 | } 236 | if c.proxyAliveHash == utils.HashSlice(oldHosts) { 237 | logrus.Debugf("alive proxy not changed, continue updating") 238 | return 239 | } 240 | c.proxyAliveHash = utils.HashSlice(oldHosts) 241 | 242 | c.m.Lock() 243 | c.availableProxyHosts = proxy.Hosts{} 244 | for _, ph := range c.proxyHosts.Hosts() { 245 | if ph.Available { 246 | c.availableProxyHosts.Add(ph) 247 | } 248 | } 249 | logrus.Infof("update %d available proxies", c.availableProxyHosts.Len()) 250 | c.m.Unlock() 251 | 252 | } 253 | 254 | // ProxyHosts returns all the proxy hosts get from URL or a static file 255 | func (c *Config) ProxyHosts() []*proxy.Host { 256 | return c.availableProxyHosts.Hosts() 257 | } 258 | -------------------------------------------------------------------------------- /config/config_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | 8 | "github.com/sirupsen/logrus" 9 | "github.com/wrfly/gus-proxy/proxy" 10 | ) 11 | 12 | func TestConfig(t *testing.T) { 13 | logrus.SetLevel(logrus.DebugLevel) 14 | c := Config{ 15 | Debug: true, 16 | ProxyFilePath: "../proxyhosts.txt", 17 | Scheduler: proxy.PING, 18 | ListenPort: "8088", 19 | } 20 | 21 | t.Run("validate config test", func(t *testing.T) { 22 | if err := c.Validate(); err != nil { 23 | t.Error(err) 24 | } 25 | }) 26 | 27 | t.Run("mock proxy not found error", func(t *testing.T) { 28 | ori := c.ProxyFilePath 29 | c.ProxyFilePath = "https://kfd.me/404" 30 | err := c.Validate() 31 | assert.Error(t, err, "not found") 32 | c.ProxyFilePath = ori 33 | }) 34 | 35 | t.Run("load hostfile", func(t *testing.T) { 36 | err := c.loadHosts() 37 | if err != nil { 38 | t.Error(err) 39 | } 40 | for _, host := range c.ProxyHosts() { 41 | t.Logf("got proxy: %s", host.Addr) 42 | } 43 | }) 44 | 45 | t.Run("update host", func(t *testing.T) { 46 | err := c.loadHosts() 47 | if err != nil { 48 | t.Error(err) 49 | } 50 | c.UpdateProxies() 51 | for _, host := range c.availableProxyHosts.Hosts() { 52 | t.Logf("got available proxy: %s", host.Addr) 53 | } 54 | }) 55 | } 56 | -------------------------------------------------------------------------------- /db/bolt.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "os" 7 | "strings" 8 | 9 | "github.com/boltdb/bolt" 10 | "github.com/sirupsen/logrus" 11 | 12 | "github.com/wrfly/gus-proxy/utils" 13 | ) 14 | 15 | var ( 16 | bktName = []byte("DNS") 17 | ) 18 | 19 | // DNS database 20 | type DNS struct { 21 | db *bolt.DB 22 | } 23 | 24 | // New database file, create one if not exist 25 | func New(dbFileName string) (*DNS, error) { 26 | if _, err := os.Stat(dbFileName); os.IsNotExist(err) { 27 | // create the file 28 | _, err := os.Create(dbFileName) 29 | if err != nil { 30 | return nil, err 31 | } 32 | } 33 | dnsDB, err := bolt.Open(dbFileName, 0600, nil) 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | dnsDB.Update(func(tx *bolt.Tx) error { 39 | _, err := tx.CreateBucket(bktName) 40 | if err != nil { 41 | return fmt.Errorf("create bucket: %s", err) 42 | } 43 | return nil 44 | }) 45 | 46 | return &DNS{ 47 | db: dnsDB, 48 | }, nil 49 | } 50 | 51 | // Close the DB 52 | func (d *DNS) Close() error { 53 | return d.db.Close() 54 | } 55 | 56 | // setDNS to the DB 57 | func (d *DNS) setDNS(domain string, answer []string) error { 58 | answerStr := strings.Join(answer, "|") 59 | err := d.db.Update(func(tx *bolt.Tx) error { 60 | b := tx.Bucket(bktName) 61 | err := b.Put([]byte(domain), []byte(answerStr)) 62 | return err 63 | }) 64 | return err 65 | } 66 | 67 | // query domain from DB 68 | func (d *DNS) query(domain string) (answer []string) { 69 | d.db.View(func(tx *bolt.Tx) error { 70 | b := tx.Bucket(bktName) 71 | v := b.Get([]byte(domain)) 72 | answer = strings.Split(string(v), "|") 73 | return nil 74 | }) 75 | if len(answer) == 0 { 76 | return nil 77 | } 78 | if answer[0] == "" { 79 | return nil 80 | } 81 | return answer 82 | } 83 | 84 | // SelectIP domain -> IP 85 | // kfd.me:8080 -> xx.x.xx.x:8080 86 | func (dnsDB *DNS) SelectIP(domainPort string) (host, hostPort string) { 87 | str := strings.Split(domainPort, ":") 88 | domain := str[0] 89 | port := "80" 90 | if len(str) == 2 { 91 | port = str[1] 92 | } 93 | 94 | ips := dnsDB.query(domain) 95 | logrus.Debugf("query %s IP: %v", domain, ips) 96 | // not found in db 97 | if len(ips) == 0 { 98 | digIPs, err := utils.LookupHost(domain) 99 | if err != nil { 100 | logrus.Errorf("Dig Error: %s", err) 101 | return "127.0.0.1", "127.0.0.1:80" 102 | } 103 | // set to db 104 | logrus.Debugf("Set DNS DB: domain: %s IP: %v", domain, digIPs) 105 | if err := dnsDB.setDNS(domain, digIPs); err != nil { 106 | logrus.Error(err) 107 | } 108 | ips = digIPs 109 | } 110 | 111 | host = ips[rand.Int()%len(ips)] 112 | hostPort = host + ":" + port 113 | 114 | return 115 | } 116 | -------------------------------------------------------------------------------- /db/db.go: -------------------------------------------------------------------------------- 1 | package db 2 | -------------------------------------------------------------------------------- /db/dolt_test.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestDB(t *testing.T) { 11 | dnsDB, err := New("/tmp/gus.db") 12 | assert.NoError(t, err) 13 | defer dnsDB.Close() 14 | 15 | err = dnsDB.setDNS("kfd.me", []string{"8.8.8.8", "1.1.1.2"}) 16 | assert.NoError(t, err) 17 | 18 | t.Run("query no error", func(t *testing.T) { 19 | a := dnsDB.query("kfd.me") 20 | for _, ip := range a { 21 | fmt.Println(ip) 22 | } 23 | }) 24 | 25 | t.Run("query not found", func(t *testing.T) { 26 | b := dnsDB.query("kfd.mee") 27 | if len(b) != 0 { 28 | t.Error("wtf?") 29 | } 30 | }) 31 | 32 | } 33 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wrfly/gus-proxy 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/boltdb/bolt v1.3.1 7 | github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a 8 | github.com/shadowsocks/go-shadowsocks2 v0.1.5 9 | github.com/sirupsen/logrus v1.9.3 10 | github.com/stretchr/testify v1.7.0 11 | github.com/urfave/cli/v2 v2.25.7 12 | golang.org/x/net v0.15.0 13 | ) 14 | 15 | require ( 16 | github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect 17 | github.com/davecgh/go-spew v1.1.1 // indirect 18 | github.com/pmezard/go-difflib v1.0.0 // indirect 19 | github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect 20 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 21 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect 22 | golang.org/x/crypto v0.13.0 // indirect 23 | golang.org/x/sys v0.12.0 // indirect 24 | gopkg.in/yaml.v3 v3.0.1 // indirect 25 | ) 26 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4= 2 | github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= 3 | github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= 4 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 5 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 7 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= 9 | github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= 10 | github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM= 11 | github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= 12 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 13 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 14 | github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg= 15 | github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s= 16 | github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= 17 | github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= 18 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 19 | github.com/shadowsocks/go-shadowsocks2 v0.1.5 h1:PDSQv9y2S85Fl7VBeOMF9StzeXZyK1HakRm86CUbr28= 20 | github.com/shadowsocks/go-shadowsocks2 v0.1.5/go.mod h1:AGGpIoek4HRno4xzyFiAtLHkOpcoznZEkAccaI/rplM= 21 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 22 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 23 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 24 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 25 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 26 | github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= 27 | github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= 28 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= 29 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= 30 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 31 | golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= 32 | golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= 33 | golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= 34 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 35 | golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= 36 | golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= 37 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 38 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 39 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 40 | golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= 41 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 42 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 43 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 44 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 45 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 46 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 47 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 48 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 49 | -------------------------------------------------------------------------------- /gus/gus.go: -------------------------------------------------------------------------------- 1 | package gus 2 | 3 | import ( 4 | "math/rand" 5 | "net" 6 | "net/http" 7 | "sort" 8 | "sync" 9 | "time" 10 | 11 | "github.com/elazarl/goproxy" 12 | "github.com/sirupsen/logrus" 13 | 14 | "github.com/wrfly/gus-proxy/config" 15 | "github.com/wrfly/gus-proxy/db" 16 | "github.com/wrfly/gus-proxy/proxy" 17 | "github.com/wrfly/gus-proxy/utils" 18 | ) 19 | 20 | type GusProxy http.Handler 21 | 22 | // Gustavo main structure 23 | type Gustavo struct { 24 | proxyHosts func() []*proxy.Host 25 | directProxy *goproxy.ProxyHttpServer 26 | noProxyList []*net.IPNet 27 | 28 | scheduler string // round-robin/random/ping 29 | randomUA bool 30 | dnsDB *db.DNS 31 | next int 32 | m sync.Mutex 33 | } 34 | 35 | func (gs *Gustavo) ServeHTTP(w http.ResponseWriter, r *http.Request) { 36 | logrus.Debugf("request: %v", r.URL) 37 | defer r.Body.Close() 38 | // keep alive all requests 39 | // r.Header.Set("Connection", "keep-alive") 40 | 41 | // rebuild request 42 | hostIP := "" 43 | hostIP, r.URL.Host = gs.dnsDB.SelectIP(r.Host) 44 | if gs.randomUA { 45 | r.Header.Set("User-Agent", utils.RandomUA()) 46 | } 47 | 48 | if gs.notProxyThisHost(hostIP) { 49 | logrus.Debugf("do not proxy this IP(%s), goto to direct", hostIP) 50 | gs.directProxy.ServeHTTP(w, r) 51 | return 52 | } 53 | 54 | selectedProxy := gs.SelectProxy() 55 | if selectedProxy != nil { 56 | logrus.Debugf("use proxy: %s", selectedProxy.Addr) 57 | selectedProxy.ServeHTTP(w, r) 58 | if w.Header().Get("PROXY_CODE") == "500" { 59 | // FIXME: potential data race 60 | selectedProxy.Available = false 61 | // proxy is down 62 | logrus.Errorf("proxy [%s] is down", selectedProxy.Addr) 63 | } 64 | return 65 | } 66 | // when there is no proxy available, we connect the target directly 67 | logrus.Error("no proxy available, direct connect") 68 | gs.directProxy.ServeHTTP(w, r) 69 | } 70 | 71 | // SelectProxy returns a proxy depends on your scheduler 72 | func (gs *Gustavo) SelectProxy() (rProxy *proxy.Host) { 73 | if len(gs.proxyHosts()) == 0 { 74 | // no proxy avaliable 75 | return nil 76 | } 77 | 78 | switch gs.scheduler { 79 | case proxy.RR: 80 | return gs.roundRobin() 81 | case proxy.RANDOM: 82 | return gs.randomProxy() 83 | default: 84 | return gs.roundRobin() 85 | } 86 | } 87 | 88 | func (gs *Gustavo) roundRobin() *proxy.Host { 89 | gs.m.Lock() 90 | defer gs.m.Unlock() 91 | 92 | ph := gs.proxyHosts() 93 | // if next greater than total num, reset to 0 94 | if gs.next >= len(ph) { 95 | gs.next = 0 96 | } 97 | gs.next++ 98 | 99 | return ph[gs.next-1] 100 | } 101 | 102 | func (gs *Gustavo) randomProxy() *proxy.Host { 103 | availableProxy := gs.proxyHosts() 104 | 105 | source := rand.NewSource(time.Now().UnixNano()) 106 | r := rand.New(source) 107 | use := r.Int() % len(availableProxy) 108 | rProxy := availableProxy[use] 109 | 110 | return rProxy 111 | } 112 | 113 | func (gs *Gustavo) pingProxy() *proxy.Host { 114 | availableProxy := gs.proxyHosts() 115 | 116 | sort.Slice(availableProxy, func(i, j int) bool { 117 | return availableProxy[i].Ping < availableProxy[j].Ping 118 | }) 119 | 120 | source := rand.NewSource(time.Now().UnixNano()) 121 | r := rand.New(source) 122 | // 随机取前1/3 || len 123 | use := r.Int() % (func() int { 124 | l := len(availableProxy) 125 | if l <= 3 { 126 | return l 127 | } 128 | return l / 3 129 | }()) 130 | rProxy := availableProxy[use] 131 | 132 | return rProxy 133 | } 134 | 135 | func (gs *Gustavo) notProxyThisHost(host string) bool { 136 | for _, n := range gs.noProxyList { 137 | target := net.ParseIP(host) 138 | if n.Contains(target) { 139 | logrus.Debugf("no proxy %s %s", n, target) 140 | return true 141 | } 142 | } 143 | return false 144 | } 145 | 146 | // New round proxy servers 147 | func New(conf *config.Config, DNSdb *db.DNS) GusProxy { 148 | logrus.Debugf("init proxy") 149 | if DNSdb == nil { 150 | logrus.Fatal("DNS DB is nil") 151 | } 152 | if len(conf.ProxyHosts()) == 0 { 153 | logrus.Fatal("no available proxy to use") 154 | } 155 | return &Gustavo{ 156 | proxyHosts: conf.ProxyHosts, 157 | scheduler: conf.Scheduler, 158 | noProxyList: conf.NoProxyCIDR, 159 | dnsDB: DNSdb, 160 | randomUA: conf.RandomUA, 161 | directProxy: goproxy.NewProxyHttpServer(), 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /gus/gus_test.go: -------------------------------------------------------------------------------- 1 | package gus 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "math/rand" 7 | "net" 8 | "net/http" 9 | "net/url" 10 | "sync" 11 | "testing" 12 | "time" 13 | 14 | "github.com/sirupsen/logrus" 15 | "github.com/stretchr/testify/assert" 16 | "github.com/wrfly/gus-proxy/config" 17 | "github.com/wrfly/gus-proxy/db" 18 | "github.com/wrfly/gus-proxy/proxy" 19 | ) 20 | 21 | func TestRoundProxy(t *testing.T) { 22 | logrus.SetLevel(logrus.DebugLevel) 23 | 24 | testNewProxy := func(t *testing.T) { 25 | ava := true 26 | hosts := []*proxy.Host{ 27 | { 28 | Addr: "socks5://127.0.0.1:1080", 29 | Available: ava, 30 | }, 31 | } 32 | for _, host := range hosts { 33 | err := host.Init() 34 | assert.NoError(t, err) 35 | } 36 | 37 | } 38 | t.Run("test new proxy", testNewProxy) 39 | 40 | time.Sleep(1 * time.Second) // release port binding 41 | 42 | serveProxy := func(t *testing.T) { 43 | // mock config 44 | c := &config.Config{ 45 | ListenPort: "54321", 46 | ProxyFilePath: "../proxyhosts.txt", 47 | Scheduler: proxy.RR, 48 | RandomUA: true, 49 | } 50 | err := c.Validate() 51 | assert.NoError(t, err) 52 | c.UpdateProxies() 53 | 54 | l, err := net.Listen("tcp4", fmt.Sprintf(":%s", c.ListenPort)) 55 | assert.NoError(t, err) 56 | assert.NotNil(t, l) 57 | 58 | DNSdb, err := db.New("/tmp/gus.db") 59 | if err != nil { 60 | logrus.Fatalf("init dns db error: %s", err) 61 | } 62 | defer DNSdb.Close() 63 | 64 | go http.Serve(l, New(c, DNSdb)) 65 | } 66 | t.Run("serve proxy", serveProxy) 67 | 68 | time.Sleep(time.Second) 69 | testWithCurl := func(t *testing.T) { 70 | proxyURL, _ := url.Parse("http://localhost:54321") 71 | clnt := &http.Client{ 72 | Transport: &http.Transport{ 73 | Proxy: http.ProxyURL(proxyURL), 74 | }, 75 | Timeout: 5 * time.Second, 76 | } 77 | var wg sync.WaitGroup 78 | for i := 0; i < 6; i++ { 79 | wg.Add(1) 80 | go func() { 81 | defer wg.Done() 82 | resp, err := clnt.Get("http://ip.kfd.me") 83 | assert.NoError(t, err) 84 | if resp == nil { 85 | return 86 | } 87 | body, err := ioutil.ReadAll(resp.Body) 88 | if err != nil { 89 | logrus.Error(err) 90 | } 91 | fmt.Printf("%s\n", body) 92 | resp.Body.Close() 93 | }() 94 | } 95 | wg.Wait() 96 | } 97 | 98 | t.Run("with curl", testWithCurl) 99 | } 100 | 101 | func generateProxys() []*proxy.Host { 102 | num := 10 103 | proxys := []*proxy.Host{} 104 | for i := 0; i < num; i++ { 105 | source := rand.NewSource(time.Now().UnixNano()) 106 | r := rand.New(source) 107 | ping := r.Float32() * 10 108 | rProxy := &proxy.Host{ 109 | Addr: fmt.Sprintf("http://proxy-%d-ping-%f", i, ping), 110 | Ping: ping, 111 | Available: true, 112 | } 113 | proxys = append(proxys, rProxy) 114 | } 115 | return proxys 116 | } 117 | 118 | func TestPingProxy(t *testing.T) { 119 | p := &Gustavo{ 120 | proxyHosts: generateProxys, 121 | scheduler: proxy.PING, 122 | } 123 | rProxy := p.pingProxy() 124 | fmt.Println("Select: ", rProxy.Addr) 125 | 126 | fmt.Println("Proxy0: ", p.proxyHosts()[0].Addr) 127 | } 128 | -------------------------------------------------------------------------------- /img/gus-curl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wrfly/gus-proxy/687bc3f160be6f8f9b517248371b8d452c42b029/img/gus-curl.png -------------------------------------------------------------------------------- /img/gus-run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wrfly/gus-proxy/687bc3f160be6f8f9b517248371b8d452c42b029/img/gus-run.png -------------------------------------------------------------------------------- /pkg/go-socks4/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Bogdan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /pkg/go-socks4/README.md: -------------------------------------------------------------------------------- 1 | # go-socks4 2 | Socks4 implementation for Go, compatible with net/proxy 3 | 4 | ## Usage 5 | ```go 6 | import ( 7 | "golang.org/x/net/proxy" 8 | _ "github.com/Bogdan-D/go-socks4" 9 | ) 10 | 11 | func main() { 12 | dialer, err := proxy.FromURL("socks4://ip:port",proxy.Direct) 13 | // check error 14 | // and use your dialer as you with 15 | } 16 | ``` 17 | 18 | 19 | ## Tests 20 | If you know proxy server to connect to tests should be running like this 21 | ` 22 | go test -socks4.address=localhost:8080 23 | ` 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /pkg/go-socks4/socks4.go: -------------------------------------------------------------------------------- 1 | package socks4 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "io" 7 | "net" 8 | "net/url" 9 | "strconv" 10 | 11 | "golang.org/x/net/proxy" 12 | ) 13 | 14 | const ( 15 | socks_version = 0x04 16 | socks_connect = 0x01 17 | socks_bind = 0x02 18 | 19 | socks_ident = "nobody@0.0.0.0" 20 | 21 | access_granted = 0x5a 22 | access_rejected = 0x5b 23 | access_identd_required = 0x5c 24 | access_identd_failed = 0x5d 25 | 26 | ErrWrongURL = "wrong server url" 27 | ErrWrongConnType = "no support for connections of type" 28 | ErrConnFailed = "connection failed to socks4 server" 29 | ErrHostUnknown = "unable to find IP address of host" 30 | ErrSocksServer = "socks4 server error" 31 | ErrConnRejected = "connection rejected" 32 | ErrIdentRequired = "socks4 server require valid identd" 33 | ) 34 | 35 | func init() { 36 | proxy.RegisterDialerType("socks4", func(u *url.URL, d proxy.Dialer) (proxy.Dialer, error) { 37 | return &socks4{url: u, dialer: d}, nil 38 | }) 39 | } 40 | 41 | type socks4Error struct { 42 | message string 43 | details interface{} 44 | } 45 | 46 | func (s *socks4Error) String() string { 47 | return s.message 48 | } 49 | 50 | func (s *socks4Error) Error() string { 51 | if s.details == nil { 52 | return s.message 53 | } 54 | 55 | return fmt.Sprintf("%s: %v", s.message, s.details) 56 | } 57 | 58 | type socks4 struct { 59 | url *url.URL 60 | dialer proxy.Dialer 61 | } 62 | 63 | func (s *socks4) Dial(network, addr string) (c net.Conn, err error) { 64 | var buf []byte 65 | 66 | switch network { 67 | case "tcp", "tcp4": 68 | default: 69 | return nil, &socks4Error{message: ErrWrongConnType, details: network} 70 | } 71 | 72 | c, err = s.dialer.Dial(network, s.url.Host) 73 | if err != nil { 74 | return nil, &socks4Error{message: ErrConnFailed, details: err} 75 | } 76 | 77 | host, port, err := net.SplitHostPort(addr) 78 | if err != nil { 79 | return nil, &socks4Error{message: ErrWrongURL, details: err} 80 | } 81 | 82 | ip, err := net.ResolveIPAddr("ip4", host) 83 | if err != nil { 84 | return nil, &socks4Error{message: ErrHostUnknown, details: err} 85 | } 86 | ip4 := ip.IP.To4() 87 | 88 | var bport [2]byte 89 | iport, _ := strconv.Atoi(port) 90 | binary.BigEndian.PutUint16(bport[:], uint16(iport)) 91 | 92 | buf = []byte{socks_version, socks_connect} 93 | buf = append(buf, bport[:]...) 94 | buf = append(buf, ip4...) 95 | buf = append(buf, socks_ident...) 96 | buf = append(buf, 0) 97 | 98 | i, err := c.Write(buf) 99 | if err != nil { 100 | return nil, &socks4Error{message: ErrSocksServer, details: err} 101 | } 102 | if l := len(buf); i != l { 103 | return nil, &socks4Error{message: ErrSocksServer, details: fmt.Sprintf("write %d bytes, expected %d", i, l)} 104 | } 105 | 106 | var resp [8]byte 107 | i, err = c.Read(resp[:]) 108 | if err != nil && err != io.EOF { 109 | return nil, &socks4Error{message: ErrSocksServer, details: err} 110 | } 111 | if i != 8 { 112 | return nil, &socks4Error{message: ErrSocksServer, details: fmt.Sprintf("read %d bytes, expected 8", i)} 113 | } 114 | 115 | switch resp[1] { 116 | case access_granted: 117 | return c, nil 118 | case access_identd_required, access_identd_failed: 119 | return nil, &socks4Error{message: ErrIdentRequired, details: strconv.FormatInt(int64(resp[1]), 16)} 120 | default: 121 | c.Close() 122 | return nil, &socks4Error{message: ErrConnRejected, details: strconv.FormatInt(int64(resp[1]), 16)} 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /pkg/go-socks4/socks4_test.go: -------------------------------------------------------------------------------- 1 | package socks4 2 | 3 | import ( 4 | "bufio" 5 | "flag" 6 | "fmt" 7 | "golang.org/x/net/proxy" 8 | "net/url" 9 | "testing" 10 | ) 11 | 12 | var address string 13 | 14 | func init() { 15 | flag.StringVar(&address, "socks4.address", "", "address of socks4 server to connect to") 16 | flag.Parse() 17 | } 18 | 19 | func TestDial(t *testing.T) { 20 | proxy_addr, _ := url.Parse(address) 21 | 22 | var socks = &socks4{url: proxy_addr, dialer: proxy.Direct} 23 | 24 | c, err := socks.Dial("tcp", "google.com:80") 25 | 26 | if err != nil { 27 | e, _ := err.(*socks4Error) 28 | 29 | switch e.String() { 30 | case ErrIdentRequired: 31 | default: 32 | t.Error(err) 33 | } 34 | } else { 35 | _, err := c.Write([]byte("GET /\n")) 36 | if err != nil { 37 | t.Error(err) 38 | } 39 | buf := bufio.NewReader(c) 40 | line, err := buf.ReadString('\n') 41 | fmt.Print(line) 42 | c.Close() 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /pkg/ss/ss.go: -------------------------------------------------------------------------------- 1 | package ss 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "net/url" 7 | 8 | "github.com/shadowsocks/go-shadowsocks2/core" 9 | "github.com/shadowsocks/go-shadowsocks2/socks" 10 | "github.com/sirupsen/logrus" 11 | "golang.org/x/net/proxy" 12 | ) 13 | 14 | func init() { 15 | proxy.RegisterDialerType("ss", ssDialer) 16 | } 17 | 18 | func ssDialer(u *url.URL, d proxy.Dialer) (proxy.Dialer, error) { 19 | if u.User == nil { 20 | return nil, fmt.Errorf("empty user") 21 | } 22 | 23 | ss := new(shadowsocks) 24 | ss.dialer = d 25 | 26 | cipherName := u.User.Username() 27 | ss.server = u.Host 28 | passwd, _ := u.User.Password() 29 | 30 | logrus.Debugf("init url: %+v %s", u, cipherName) 31 | cipher, err := core.PickCipher(cipherName, []byte{}, passwd) 32 | if err != nil { 33 | return nil, fmt.Errorf("pick cipher err: %s", err) 34 | } 35 | ss.cipher = cipher 36 | 37 | return ss, nil 38 | } 39 | 40 | type shadowsocks struct { 41 | dialer proxy.Dialer 42 | 43 | cipher core.Cipher 44 | server string 45 | } 46 | 47 | func (ss *shadowsocks) Dial(network, addr string) (net.Conn, error) { 48 | if network != "tcp" { 49 | return ss.dialer.Dial(network, addr) 50 | } 51 | 52 | rc, err := net.Dial("tcp", ss.server) 53 | if err != nil { 54 | return nil, fmt.Errorf("dial target err: %s", err) 55 | } 56 | rc = ss.cipher.StreamConn(rc) 57 | rc.Write(socks.ParseAddr(addr)) 58 | return rc, nil 59 | } 60 | -------------------------------------------------------------------------------- /proxy/hosts.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "net" 7 | "net/http" 8 | "net/url" 9 | "sync" 10 | "time" 11 | 12 | "github.com/elazarl/goproxy" 13 | "github.com/sirupsen/logrus" 14 | "golang.org/x/net/proxy" 15 | 16 | "github.com/wrfly/gus-proxy/utils" 17 | ) 18 | 19 | var localIP string 20 | 21 | func init() { 22 | ip, err := utils.PublicIP() 23 | if err != nil { 24 | panic(err) 25 | } 26 | localIP = ip 27 | } 28 | 29 | type Hosts struct { 30 | hosts []*Host 31 | m sync.RWMutex 32 | } 33 | 34 | func (h *Hosts) Add(p *Host) { 35 | h.m.Lock() 36 | h.hosts = append(h.hosts, p) 37 | h.m.Unlock() 38 | } 39 | 40 | func (h *Hosts) Hosts() []*Host { 41 | return h.hosts 42 | } 43 | 44 | func (h *Hosts) Len() int32 { 45 | return int32(len(h.hosts)) 46 | } 47 | 48 | func (h *Hosts) Host(i int) *Host { 49 | h.m.RLock() 50 | l := len(h.hosts) 51 | if l <= i { 52 | return nil 53 | } 54 | p := h.hosts[i] 55 | h.m.RUnlock() 56 | return p 57 | } 58 | 59 | // Host defines the proxy 60 | type Host struct { 61 | Type string // http or socks5 or direct 62 | Addr string // 127.0.0.1:1080 63 | Ping float32 // 66 ms 64 | Available bool 65 | Auth proxy.Auth 66 | 67 | u *url.URL 68 | proxy *goproxy.ProxyHttpServer 69 | } 70 | 71 | func (host *Host) ServeHTTP(w http.ResponseWriter, r *http.Request) { 72 | host.proxy.ServeHTTP(w, r) 73 | } 74 | 75 | func (host *Host) Init() (err error) { 76 | logrus.Debugf("init proxy [%s]", host.Addr) 77 | host.u, err = url.Parse(host.Addr) 78 | if err != nil { 79 | return err 80 | } 81 | 82 | if host.u.Scheme != "direct" { 83 | conn, err := net.DialTimeout("tcp", host.u.Host, 3*time.Second) 84 | if err != nil { 85 | return err 86 | } 87 | conn.Close() 88 | } 89 | 90 | return initGoProxy(host) 91 | } 92 | 93 | func (host *Host) CheckAvaliable() (err error) { 94 | logrus.Debugf("check [%s] avaliable", host.Addr) 95 | 96 | cli := &http.Client{Timeout: 3 * time.Second} 97 | if host.proxy != nil { 98 | cli.Transport = host.proxy.Tr 99 | } 100 | defer cli.CloseIdleConnections() 101 | 102 | resp, err := cli.Get("http://ip.kfd.me") 103 | if err != nil { 104 | return fmt.Errorf("request failed: %s", err) 105 | } 106 | defer resp.Body.Close() 107 | 108 | bs, err := ioutil.ReadAll(resp.Body) 109 | if err != nil { 110 | return err 111 | } 112 | if len(bs) >= 1 { 113 | bs = bs[:len(bs)-1] 114 | } 115 | logrus.Debugf("proxy [%s] got IP %s", host.Addr, bs) 116 | if host.Type != DIRECT && string(bs) == localIP { 117 | return fmt.Errorf("bad proxy [%s], same IP", host.Addr) 118 | } 119 | 120 | host.Available = true 121 | // TODO: ping is not avaliable since different operating system 122 | // has different ping 123 | // host.Ping = utils.Ping(host.u.Hostname()) 124 | 125 | return nil 126 | } 127 | -------------------------------------------------------------------------------- /proxy/proxy.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | "net/http" 7 | "net/url" 8 | "strings" 9 | 10 | "github.com/elazarl/goproxy" 11 | "github.com/sirupsen/logrus" 12 | "golang.org/x/net/proxy" 13 | 14 | // socks4 proxy 15 | _ "github.com/wrfly/gus-proxy/pkg/go-socks4" 16 | // shadowsocks proxy 17 | _ "github.com/wrfly/gus-proxy/pkg/ss" 18 | ) 19 | 20 | // ... 21 | const ( 22 | AuthHeader = "Proxy-Authorization" 23 | ) 24 | 25 | func setBasicAuth(username, password string, req *http.Request) { 26 | req.Header.Set(AuthHeader, fmt.Sprintf("Basic %s", basicAuth(username, password))) 27 | } 28 | 29 | func basicAuth(username, password string) string { 30 | return base64.StdEncoding.EncodeToString([]byte(username + ":" + password)) 31 | } 32 | 33 | func proxyHTTP(httpAddr string) (*goproxy.ProxyHttpServer, error) { 34 | proxyURL, err := url.Parse(httpAddr) 35 | if err != nil { 36 | return nil, err 37 | } 38 | logrus.Debugf("new HTTP proxy Host: %s, Port: %s", proxyURL.Host, proxyURL.Port()) 39 | 40 | proxyServer := goproxy.NewProxyHttpServer() 41 | proxyServer.Tr.Proxy = http.ProxyURL(proxyURL) 42 | proxyServer.OnRequest().HandleConnect(goproxy.AlwaysMitm) 43 | 44 | if proxyURL.User.String() != "" { 45 | pass, _ := proxyURL.User.Password() 46 | user := proxyURL.User.Username() 47 | proxyServer.ConnectDial = proxyServer. 48 | NewConnectDialToProxyWithHandler( 49 | proxyURL.String(), 50 | func(req *http.Request) { 51 | setBasicAuth(user, pass, req) 52 | }, 53 | ) 54 | proxyServer.OnRequest().DoFunc(func(req *http.Request, 55 | ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { 56 | setBasicAuth(user, pass, req) 57 | return req, nil 58 | }) 59 | } 60 | 61 | return proxyServer, nil 62 | } 63 | 64 | func proxySocks4(u *url.URL) (*goproxy.ProxyHttpServer, error) { 65 | dialer, err := proxy.FromURL(u, proxy.Direct) 66 | if err != nil { 67 | return nil, err 68 | } 69 | 70 | prox := goproxy.NewProxyHttpServer() 71 | prox.Tr.Dial = dialer.Dial 72 | 73 | return prox, nil 74 | } 75 | 76 | func proxySocks5(socks5Addr string, auth proxy.Auth) (*goproxy.ProxyHttpServer, error) { 77 | 78 | dialer, err := proxy.SOCKS5("tcp", socks5Addr, &auth, proxy.Direct) 79 | if err != nil { 80 | return nil, err 81 | } 82 | 83 | prox := goproxy.NewProxyHttpServer() 84 | prox.Tr.Dial = dialer.Dial 85 | 86 | return prox, nil 87 | } 88 | 89 | func proxyDirect() *goproxy.ProxyHttpServer { 90 | return goproxy.NewProxyHttpServer() 91 | } 92 | 93 | func initGoProxy(host *Host) error { 94 | var ( 95 | err error 96 | hostAndPort string 97 | ) 98 | 99 | var u *url.URL 100 | u, host.Auth, host.Type, hostAndPort = splitURL(host.Addr) 101 | switch host.Type { 102 | case DIRECT: 103 | host.proxy = proxyDirect() 104 | case HTTP: 105 | host.proxy, err = proxyHTTP(host.Addr) 106 | case SOCKS5: 107 | host.proxy, err = proxySocks5(hostAndPort, host.Auth) 108 | case HTTPS: 109 | // TODO: 110 | // host.proxy, err = proxyHTTPS(host.Addr) 111 | case ShadorSocks, SOCKS4: 112 | host.proxy, err = proxySocks4(u) 113 | 114 | default: 115 | return fmt.Errorf("[%s]: unknown protocol %s", host.Addr, host.Type) 116 | } 117 | 118 | return err 119 | } 120 | 121 | func splitURL(URL string) (u *url.URL, auth proxy.Auth, scheme, host string) { 122 | var err error 123 | u, err = url.Parse(URL) 124 | if err != nil { 125 | return 126 | } 127 | scheme = u.Scheme 128 | host = u.Host 129 | if u.User != nil { 130 | auth.User = u.User.Username() 131 | auth.Password, _ = u.User.Password() 132 | } 133 | 134 | return u, auth, strings.ToLower(scheme), host 135 | } 136 | -------------------------------------------------------------------------------- /proxy/proxy_test.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "io/ioutil" 5 | "net/http" 6 | "os" 7 | "testing" 8 | 9 | "github.com/sirupsen/logrus" 10 | "github.com/stretchr/testify/assert" 11 | "golang.org/x/net/proxy" 12 | ) 13 | 14 | func TestHttpProxy(t *testing.T) { 15 | addr := "http://127.0.0.1:1081" 16 | p, err := proxyHTTP(addr) 17 | assert.NoError(t, err) 18 | 19 | c := http.Client{Transport: p.Tr} 20 | r, _ := http.NewRequest("GET", "http://ip.kfd.me", nil) 21 | resp, err := c.Do(r) 22 | if err != nil { 23 | t.Fatal(err) 24 | } 25 | defer resp.Body.Close() 26 | 27 | bs, err := ioutil.ReadAll(resp.Body) 28 | if err != nil { 29 | t.Fatal(err) 30 | } 31 | t.Logf("%s", bs) 32 | } 33 | 34 | func TestSocks5Proxy(t *testing.T) { 35 | addr := "127.0.0.1:1080" 36 | p, err := proxySocks5(addr, proxy.Auth{}) 37 | assert.NoError(t, err) 38 | 39 | c := http.Client{Transport: p.Tr} 40 | r, _ := http.NewRequest("GET", "http://ip.kfd.me", nil) 41 | resp, err := c.Do(r) 42 | if err != nil { 43 | t.Fatal(err) 44 | } 45 | defer resp.Body.Close() 46 | 47 | bs, err := ioutil.ReadAll(resp.Body) 48 | if err != nil { 49 | t.Fatal(err) 50 | } 51 | t.Logf("%s", bs) 52 | } 53 | 54 | func TestDirect(t *testing.T) { 55 | p := proxyDirect() 56 | 57 | c := http.Client{Transport: p.Tr} 58 | r, _ := http.NewRequest("GET", "http://ip.kfd.me", nil) 59 | r.Header.Set("User-Agent", "curl") 60 | resp, err := c.Do(r) 61 | if err != nil { 62 | t.Fatal(err) 63 | } 64 | defer resp.Body.Close() 65 | 66 | bs, err := ioutil.ReadAll(resp.Body) 67 | if err != nil { 68 | t.Fatal(err) 69 | } 70 | t.Logf("%s", bs) 71 | 72 | } 73 | 74 | func TestInitProxy(t *testing.T) { 75 | logrus.SetLevel(logrus.DebugLevel) 76 | logrus.SetOutput(os.Stdout) 77 | 78 | hosts := []*Host{ 79 | {Addr: "socks5://127.0.0.1:1080"}, 80 | {Addr: "https://127.0.0.1:1081"}, 81 | {Addr: "direct://0.0.0.0"}, 82 | } 83 | for _, host := range hosts { 84 | err := host.Init() 85 | assert.NoError(t, err) 86 | t.Logf("%s: %v\n", host.Addr, host.Available) 87 | } 88 | } 89 | 90 | func TestSplitURL(t *testing.T) { 91 | URLS := []string{ 92 | "http://localhost:8080", 93 | "http://u:p@localhost:8080", 94 | "socks5://localhost:8080", 95 | "socks5://usner:ocxasd@localhost:8080", 96 | "https://usnerocxasd@localhost:8080", 97 | } 98 | for _, U := range URLS { 99 | _, auth, scheme, hostAndPort := splitURL(U) 100 | t.Log(auth, scheme, hostAndPort) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /proxy/types.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | // supported chose method 4 | const ( 5 | RR = "round_robin" 6 | RANDOM = "random" 7 | PING = "ping" 8 | ) 9 | 10 | // supported protocols 11 | const ( 12 | DIRECT = "direct" 13 | HTTP = "http" 14 | HTTPS = "https" 15 | SOCKS4 = "socks4" 16 | SOCKS5 = "socks5" 17 | 18 | // shadowsocks protocol 19 | ShadorSocks = "ss" 20 | ) 21 | -------------------------------------------------------------------------------- /proxyhosts.txt: -------------------------------------------------------------------------------- 1 | socks5://127.0.0.1:1080 2 | http://127.0.0.1:1081 3 | socks4://127.0.0.1:1082 4 | direct://0.0.0.0 5 | ss://AEAD_CHACHA20_POLY1305:passwd!@1.2.3.4:34567 -------------------------------------------------------------------------------- /test/ip/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io/ioutil" 5 | "log" 6 | "net/http" 7 | "os" 8 | "sync" 9 | "sync/atomic" 10 | "time" 11 | ) 12 | 13 | var logE = log.New(os.Stderr, "[ERR] ", log.Ltime) 14 | var logI = log.New(os.Stderr, "[INFO] ", log.Ltime) 15 | 16 | func main() { 17 | c := http.Client{ 18 | Transport: &http.Transport{ 19 | Proxy: http.ProxyFromEnvironment, 20 | }, 21 | Timeout: time.Second * 5, 22 | } 23 | 24 | var ( 25 | wg sync.WaitGroup 26 | successed uint32 27 | failed uint32 28 | ipMap = make(map[string]bool, 100) 29 | 30 | start = time.Now() 31 | ) 32 | 33 | for i := 0; i < 1e2; i++ { 34 | wg.Add(1) 35 | go func() { 36 | var ( 37 | resp *http.Response 38 | bs []byte 39 | err error 40 | ) 41 | defer func() { 42 | wg.Done() 43 | if err != nil || bs == nil { 44 | atomic.AddUint32(&failed, 1) 45 | if err == nil { 46 | logE.Printf("bad length of bytes: %d\n", len(bs)) 47 | } else { 48 | logE.Println(err) 49 | logE.Printf("%s", bs) 50 | } 51 | return 52 | } 53 | remoteIP := string(bs) 54 | logI.Printf("IP: %s", remoteIP) 55 | ipMap[remoteIP] = true 56 | atomic.AddUint32(&successed, 1) 57 | }() 58 | 59 | resp, err = c.Get("http://ip.kfd.me") 60 | if err != nil { 61 | return 62 | } 63 | defer resp.Body.Close() 64 | bs, err = ioutil.ReadAll(resp.Body) 65 | }() 66 | } 67 | 68 | wg.Wait() 69 | logI.Printf("UniqIP: %d, OK: %d, Bad: %d, Use: %s", 70 | len(ipMap), successed, failed, use(start)) 71 | } 72 | 73 | func use(s time.Time) time.Duration { 74 | return time.Now().Sub(s).Truncate(time.Millisecond) 75 | } 76 | -------------------------------------------------------------------------------- /test/ip/runtime.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wrfly/gus-proxy/687bc3f160be6f8f9b517248371b8d452c42b029/test/ip/runtime.png -------------------------------------------------------------------------------- /tools/scripts/proxy_spider.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # prepare 4 | git clone https://github.com/zhangchenchen/proxyspider.git 5 | cd proxyspider 6 | pip install -r requirements.txt 7 | 8 | # fetch proxys 9 | python proxyspider.py 10 | 11 | cat proxy_list.txt | cut -d " " -f1 | grep ":" | sed 's#^#http://#g' -------------------------------------------------------------------------------- /tools/thief/hidemyna.me/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // steal proxies from 4 | // https://hidemyna.me/en/proxy-list/?country=TW#list 5 | 6 | func main() { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /utils/dig.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "net" 5 | ) 6 | 7 | // LookupHost ... 8 | func LookupHost(domain string) (IPs []string, err error) { 9 | ips, err := net.LookupIP(domain) 10 | if err != nil { 11 | return nil, err 12 | } 13 | 14 | ipv4 := make([]string, 0, len(ips)) 15 | for _, ip := range ips { 16 | if ip.To4() == nil { // not an ipv4 address 17 | continue 18 | } 19 | ipv4 = append(ipv4, ip.String()) 20 | } 21 | 22 | return ipv4[:], nil 23 | 24 | } 25 | -------------------------------------------------------------------------------- /utils/hash.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "crypto/md5" 5 | "fmt" 6 | "sort" 7 | "strings" 8 | ) 9 | 10 | func HashSlice(slice []string) string { 11 | sort.Strings(slice) 12 | s := strings.Join(slice, "") 13 | return fmt.Sprintf("%x", md5.New().Sum([]byte(s))) 14 | } 15 | -------------------------------------------------------------------------------- /utils/ip.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "io/ioutil" 5 | "net/http" 6 | "time" 7 | ) 8 | 9 | func PublicIP() (string, error) { 10 | c := http.Client{ 11 | Timeout: time.Second * 3, 12 | } 13 | resp, err := c.Get("http://i.kfd.me") 14 | if err != nil { 15 | return "", err 16 | } 17 | defer resp.Body.Close() 18 | bs, err := ioutil.ReadAll(resp.Body) 19 | return string(bs), err 20 | } 21 | -------------------------------------------------------------------------------- /utils/ping.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | // Ping host and returns the average rtt 4 | func Ping(ip string) float32 { 5 | return 10 6 | 7 | // TODO: different operating system has different ping command, 8 | // can not use go-ping https://github.com/sparrc/go-ping 9 | // because it has to set extra system settings 10 | // (sudo sysctl -w net.ipv4.ping_group_range="0 2147483647") 11 | 12 | // alpine: 13 | // BusyBox v1.28.4 (2018-05-30 10:45:57 UTC) multi-call binary. 14 | // Usage: ping [OPTIONS] HOST 15 | 16 | // Send ICMP ECHO_REQUEST packets to network hosts 17 | 18 | // -4,-6 Force IP or IPv6 name resolution 19 | // -c CNT Send only CNT pings 20 | // -s SIZE Send SIZE data bytes in packets (default 56) 21 | // -t TTL Set TTL 22 | // -I IFACE/IP Source interface or IP address 23 | // -W SEC Seconds to wait for the first response (default 10) 24 | // (after all -c CNT packets are sent) 25 | // -w SEC Seconds until ping exits (default:infinite) 26 | // (can exit earlier with -c CNT) 27 | // -q Quiet, only display output at start 28 | // and when finished 29 | // -p HEXBYTE Pattern to use for payload 30 | 31 | // darwin: 32 | // ping: option requires an argument -- h 33 | // usage: ping [-AaDdfnoQqRrv] [-c count] [-G sweepmaxsize] 34 | // [-g sweepminsize] [-h sweepincrsize] [-i wait] 35 | // [-l preload] [-M mask | time] [-m ttl] [-p pattern] 36 | // [-S src_addr] [-s packetsize] [-t timeout][-W waittime] 37 | // [-z tos] host 38 | // ping [-AaDdfLnoQqRrv] [-c count] [-I iface] [-i wait] 39 | // [-l preload] [-M mask | time] [-m ttl] [-p pattern] [-S src_addr] 40 | // [-s packetsize] [-T ttl] [-t timeout] [-W waittime] 41 | // [-z tos] mcast-group 42 | // Apple specific options (to be specified before mcast-group or host like all options) 43 | // -b boundif # bind the socket to the interface 44 | // -k traffic_class # set traffic class socket option 45 | // -K net_service_type # set traffic class socket options 46 | // -apple-connect # call connect(2) in the socket 47 | // -apple-time # display current time 48 | 49 | // cmd := exec.Command("ping", "-A", "-c", "3", "-w", "2", ip) 50 | // b, err := cmd.Output() 51 | // if err != nil { 52 | // return 9999 53 | // } 54 | 55 | // o := string(b) 56 | // l := strings.LastIndex(o, "=") 57 | // o = string(o[l+2:]) 58 | // s := strings.Split(o, "/") 59 | 60 | // avg := s[1] 61 | 62 | // f, _ := strconv.ParseFloat(avg, 32) 63 | 64 | // return float32(f) 65 | } 66 | -------------------------------------------------------------------------------- /utils/ua.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | ) 7 | 8 | var userAgents = []string{ 9 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.246", 10 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/7046A194A", 11 | "Mozilla/5.0 (iPad; CPU OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5355d Safari/8536.25", 12 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.13+ (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2", 13 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/534.55.3 (KHTML, like Gecko) Version/5.1.3 Safari/534.53.10", 14 | "Opera/9.80 (X11; Linux i686; Ubuntu/14.10) Presto/2.12.388 Version/12.16", 15 | "Opera/9.80 (Windows NT 6.0) Presto/2.12.388 Version/12.14", 16 | "Mozilla/5.0 (Windows NT 6.0; rv:2.0) Gecko/20100101 Firefox/4.0 Opera 12.14", 17 | "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.0) Opera 12.14", 18 | "Opera/9.80 (Windows NT 6.1; U; es-ES) Presto/2.9.181 Version/12.00", 19 | "Opera/9.80 (Windows NT 5.1; U; zh-sg) Presto/2.9.181 Version/12.00", 20 | "Opera/12.0(Windows NT 5.2;U;en)Presto/22.9.168 Version/12.00", 21 | "Opera/12.0(Windows NT 5.1;U;en)Presto/22.9.168 Version/12.00", 22 | "Mozilla/5.0 (Windows NT 5.1) Gecko/20100101 Firefox/14.0 Opera/12.0", 23 | "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/533.1 (KHTML, like Gecko) Maxthon/3.0.8.2 Safari/533.1", 24 | "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.0; Trident/4.0; Maxthon; SV1; .NET CLR 1.1.4322; .NET CLR 2.4.84947; SLCC1; Media Center PC 4.0; Zune 3.5; Tablet PC 3.5; InfoPath.3)", 25 | "Mozilla/5.0 (X11; U; Linux i686; fr-fr) AppleWebKit/525.1+ (KHTML, like Gecko, Safari/525.1+) midori/1.19", 26 | "Mozilla/5.0 (Windows; U; Windows NT 6.1; rv:2.2) Gecko/20110201", 27 | "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36", 28 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.1 Safari/537.36", 29 | "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36", 30 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36", 31 | "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2226.0 Safari/537.36", 32 | "Mozilla/5.0 (Windows NT 6.4; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2225.0 Safari/537.36", 33 | "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2225.0 Safari/537.36", 34 | "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2224.3 Safari/537.36", 35 | "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.93 Safari/537.36", 36 | "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1", 37 | "Mozilla/5.0 (Windows NT 6.3; rv:36.0) Gecko/20100101 Firefox/36.0", 38 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10; rv:33.0) Gecko/20100101 Firefox/33.0", 39 | "Mozilla/5.0 (X11; Linux i586; rv:31.0) Gecko/20100101 Firefox/31.0", 40 | "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:31.0) Gecko/20130401 Firefox/31.0", 41 | "Mozilla/5.0 (Windows NT 5.1; rv:31.0) Gecko/20100101 Firefox/31.0", 42 | "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:29.0) Gecko/20120101 Firefox/29.0", 43 | "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:25.0) Gecko/20100101 Firefox/29.0", 44 | "Mozilla/5.0 (X11; OpenBSD amd64; rv:28.0) Gecko/20100101 Firefox/28.0", 45 | "Mozilla/5.0 (X11; Linux x86_64; rv:28.0) Gecko/20100101 Firefox/28.0", 46 | } 47 | 48 | // RandomUA returns a random user-agent 49 | func RandomUA() string { 50 | source := rand.NewSource(time.Now().UnixNano()) 51 | r := rand.New(source) 52 | i := r.Int() % len(userAgents) 53 | return userAgents[i] 54 | } 55 | -------------------------------------------------------------------------------- /utils/utils_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func TestPing(t *testing.T) { 10 | t.Log(Ping("8.8.8.8")) 11 | t.Log(Ping("1.1.1.1")) 12 | t.Log(Ping("kfd.me")) 13 | } 14 | 15 | func TestParsePing(t *testing.T) { 16 | o := `PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data. 17 | 64 bytes from 8.8.8.8: icmp_seq=1 ttl=39 time=63.4 ms 18 | 64 bytes from 8.8.8.8: icmp_seq=2 ttl=39 time=59.4 ms 19 | 64 bytes from 8.8.8.8: icmp_seq=3 ttl=39 time=69.9 ms 20 | 21 | --- 8.8.8.8 ping statistics --- 22 | 3 packets transmitted, 3 received, 0% packet loss, time 401ms 23 | rtt min/avg/max/mdev = 59.498/64.273/69.909/4.303 ms, ipg/ewma 200.829/63.797 ms` 24 | 25 | l := strings.LastIndex(o, "=") 26 | o = string(o[l+2:]) 27 | s := strings.Split(o, "/") 28 | 29 | fmt.Println(s[1]) 30 | 31 | } 32 | 33 | func TestSelectUA(t *testing.T) { 34 | ua1, ua2 := RandomUA(), RandomUA() 35 | if ua1 == ua2 { 36 | t.Error("random ua failed") 37 | } 38 | } 39 | 40 | func TestHashSlice(t *testing.T) { 41 | slice := []string{"1", "2", "3"} 42 | if HashSlice(slice) != HashSlice(slice) { 43 | t.Error("not equal") 44 | } 45 | 46 | if HashSlice(slice) == HashSlice(append(slice, "4")) { 47 | t.Error("equal") 48 | } 49 | } 50 | 51 | func TestPubIP(t *testing.T) { 52 | ip, err := PublicIP() 53 | if err != nil { 54 | t.Error(err) 55 | } 56 | t.Log(ip) 57 | } 58 | 59 | func TestLookupHosts(t *testing.T) { 60 | ips, err := LookupHost("kfd.me") 61 | if err != nil { 62 | t.Error(err) 63 | } 64 | t.Logf("%v", ips) 65 | } 66 | --------------------------------------------------------------------------------