├── .gitignore ├── .vscode ├── c_cpp_properties.json └── settings.json ├── LICENSE ├── README.md ├── build.zig ├── relay.conf ├── src ├── Config.zig ├── DynStr.zig ├── EvLoop.zig ├── ListNode.zig ├── Rc.zig ├── StrList.zig ├── c.zig ├── cc.zig ├── cfg.zig ├── cfg_checker.zig ├── cfg_loader.zig ├── co.zig ├── flags_op.zig ├── fmtchk.zig ├── g.zig ├── in.zig ├── in_socks.zig ├── in_tlsproxy.zig ├── in_tproxy.zig ├── in_trojan.zig ├── log.c ├── log.h ├── log.zig ├── main.zig ├── misc.c ├── misc.h ├── modules.zig ├── net.c ├── net.h ├── net.zig ├── opt.zig ├── out.zig ├── out_raw.zig ├── out_socks.zig ├── out_tlsproxy.zig ├── out_trojan.zig ├── sentinel_vector.zig ├── server.zig ├── str2int.zig ├── tests.zig ├── wolfssl.h └── wolfssl_opt.h └── zls.build.json /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.gch 3 | zig-out/ 4 | zig-cache/ 5 | dep/wolfssl-* 6 | -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Linux", 5 | "intelliSenseMode": "linux-clang-x64", 6 | "compilerPath": "/usr/bin/clang", 7 | "compilerArgs": [ 8 | "-I${workspaceFolder}/dep/wolfssl-5.7.0-stable" 9 | ], 10 | "defines": [ 11 | "TEST", 12 | "MUSL", 13 | "ENABLE_WOLFSSL" 14 | ], 15 | "cStandard": "c11", 16 | "cppStandard": "c++17" 17 | } 18 | ], 19 | "version": 4 20 | } 21 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "*.h": "c", 4 | "*.i": "c", 5 | "*.in": "c", 6 | "*.S": "c", 7 | }, 8 | } 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU AFFERO GENERAL PUBLIC LICENSE 2 | Version 3, 19 November 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 Affero General Public License is a free, copyleft license for 11 | software and other kinds of works, specifically designed to ensure 12 | cooperation with the community in the case of network server software. 13 | 14 | The licenses for most software and other practical works are designed 15 | to take away your freedom to share and change the works. By contrast, 16 | our General Public Licenses are intended to guarantee your freedom to 17 | share and change all versions of a program--to make sure it remains free 18 | software for all its users. 19 | 20 | When we speak of free software, we are referring to freedom, not 21 | price. Our General Public Licenses are designed to make sure that you 22 | have the freedom to distribute copies of free software (and charge for 23 | them if you wish), that you receive source code or can get it if you 24 | want it, that you can change the software or use pieces of it in new 25 | free programs, and that you know you can do these things. 26 | 27 | Developers that use our General Public Licenses protect your rights 28 | with two steps: (1) assert copyright on the software, and (2) offer 29 | you this License which gives you legal permission to copy, distribute 30 | and/or modify the software. 31 | 32 | A secondary benefit of defending all users' freedom is that 33 | improvements made in alternate versions of the program, if they 34 | receive widespread use, become available for other developers to 35 | incorporate. Many developers of free software are heartened and 36 | encouraged by the resulting cooperation. However, in the case of 37 | software used on network servers, this result may fail to come about. 38 | The GNU General Public License permits making a modified version and 39 | letting the public access it on a server without ever releasing its 40 | source code to the public. 41 | 42 | The GNU Affero General Public License is designed specifically to 43 | ensure that, in such cases, the modified source code becomes available 44 | to the community. It requires the operator of a network server to 45 | provide the source code of the modified version running there to the 46 | users of that server. Therefore, public use of a modified version, on 47 | a publicly accessible server, gives the public access to the source 48 | code of the modified version. 49 | 50 | An older license, called the Affero General Public License and 51 | published by Affero, was designed to accomplish similar goals. This is 52 | a different license, not a version of the Affero GPL, but Affero has 53 | released a new version of the Affero GPL which permits relicensing under 54 | this license. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | TERMS AND CONDITIONS 60 | 61 | 0. Definitions. 62 | 63 | "This License" refers to version 3 of the GNU Affero General Public License. 64 | 65 | "Copyright" also means copyright-like laws that apply to other kinds of 66 | works, such as semiconductor masks. 67 | 68 | "The Program" refers to any copyrightable work licensed under this 69 | License. Each licensee is addressed as "you". "Licensees" and 70 | "recipients" may be individuals or organizations. 71 | 72 | To "modify" a work means to copy from or adapt all or part of the work 73 | in a fashion requiring copyright permission, other than the making of an 74 | exact copy. The resulting work is called a "modified version" of the 75 | earlier work or a work "based on" the earlier work. 76 | 77 | A "covered work" means either the unmodified Program or a work based 78 | on the Program. 79 | 80 | To "propagate" a work means to do anything with it that, without 81 | permission, would make you directly or secondarily liable for 82 | infringement under applicable copyright law, except executing it on a 83 | computer or modifying a private copy. Propagation includes copying, 84 | distribution (with or without modification), making available to the 85 | public, and in some countries other activities as well. 86 | 87 | To "convey" a work means any kind of propagation that enables other 88 | parties to make or receive copies. Mere interaction with a user through 89 | a computer network, with no transfer of a copy, is not conveying. 90 | 91 | An interactive user interface displays "Appropriate Legal Notices" 92 | to the extent that it includes a convenient and prominently visible 93 | feature that (1) displays an appropriate copyright notice, and (2) 94 | tells the user that there is no warranty for the work (except to the 95 | extent that warranties are provided), that licensees may convey the 96 | work under this License, and how to view a copy of this License. If 97 | the interface presents a list of user commands or options, such as a 98 | menu, a prominent item in the list meets this criterion. 99 | 100 | 1. Source Code. 101 | 102 | The "source code" for a work means the preferred form of the work 103 | for making modifications to it. "Object code" means any non-source 104 | form of a work. 105 | 106 | A "Standard Interface" means an interface that either is an official 107 | standard defined by a recognized standards body, or, in the case of 108 | interfaces specified for a particular programming language, one that 109 | is widely used among developers working in that language. 110 | 111 | The "System Libraries" of an executable work include anything, other 112 | than the work as a whole, that (a) is included in the normal form of 113 | packaging a Major Component, but which is not part of that Major 114 | Component, and (b) serves only to enable use of the work with that 115 | Major Component, or to implement a Standard Interface for which an 116 | implementation is available to the public in source code form. A 117 | "Major Component", in this context, means a major essential component 118 | (kernel, window system, and so on) of the specific operating system 119 | (if any) on which the executable work runs, or a compiler used to 120 | produce the work, or an object code interpreter used to run it. 121 | 122 | The "Corresponding Source" for a work in object code form means all 123 | the source code needed to generate, install, and (for an executable 124 | work) run the object code and to modify the work, including scripts to 125 | control those activities. However, it does not include the work's 126 | System Libraries, or general-purpose tools or generally available free 127 | programs which are used unmodified in performing those activities but 128 | which are not part of the work. For example, Corresponding Source 129 | includes interface definition files associated with source files for 130 | the work, and the source code for shared libraries and dynamically 131 | linked subprograms that the work is specifically designed to require, 132 | such as by intimate data communication or control flow between those 133 | subprograms and other parts of the work. 134 | 135 | The Corresponding Source need not include anything that users 136 | can regenerate automatically from other parts of the Corresponding 137 | Source. 138 | 139 | The Corresponding Source for a work in source code form is that 140 | same work. 141 | 142 | 2. Basic Permissions. 143 | 144 | All rights granted under this License are granted for the term of 145 | copyright on the Program, and are irrevocable provided the stated 146 | conditions are met. This License explicitly affirms your unlimited 147 | permission to run the unmodified Program. The output from running a 148 | covered work is covered by this License only if the output, given its 149 | content, constitutes a covered work. This License acknowledges your 150 | rights of fair use or other equivalent, as provided by copyright law. 151 | 152 | You may make, run and propagate covered works that you do not 153 | convey, without conditions so long as your license otherwise remains 154 | in force. You may convey covered works to others for the sole purpose 155 | of having them make modifications exclusively for you, or provide you 156 | with facilities for running those works, provided that you comply with 157 | the terms of this License in conveying all material for which you do 158 | not control copyright. Those thus making or running the covered works 159 | for you must do so exclusively on your behalf, under your direction 160 | and control, on terms that prohibit them from making any copies of 161 | your copyrighted material outside their relationship with you. 162 | 163 | Conveying under any other circumstances is permitted solely under 164 | the conditions stated below. Sublicensing is not allowed; section 10 165 | makes it unnecessary. 166 | 167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 168 | 169 | No covered work shall be deemed part of an effective technological 170 | measure under any applicable law fulfilling obligations under article 171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 172 | similar laws prohibiting or restricting circumvention of such 173 | measures. 174 | 175 | When you convey a covered work, you waive any legal power to forbid 176 | circumvention of technological measures to the extent such circumvention 177 | is effected by exercising rights under this License with respect to 178 | the covered work, and you disclaim any intention to limit operation or 179 | modification of the work as a means of enforcing, against the work's 180 | users, your or third parties' legal rights to forbid circumvention of 181 | technological measures. 182 | 183 | 4. Conveying Verbatim Copies. 184 | 185 | You may convey verbatim copies of the Program's source code as you 186 | receive it, in any medium, provided that you conspicuously and 187 | appropriately publish on each copy an appropriate copyright notice; 188 | keep intact all notices stating that this License and any 189 | non-permissive terms added in accord with section 7 apply to the code; 190 | keep intact all notices of the absence of any warranty; and give all 191 | recipients a copy of this License along with the Program. 192 | 193 | You may charge any price or no price for each copy that you convey, 194 | and you may offer support or warranty protection for a fee. 195 | 196 | 5. Conveying Modified Source Versions. 197 | 198 | You may convey a work based on the Program, or the modifications to 199 | produce it from the Program, in the form of source code under the 200 | terms of section 4, provided that you also meet all of these conditions: 201 | 202 | a) The work must carry prominent notices stating that you modified 203 | it, and giving a relevant date. 204 | 205 | b) The work must carry prominent notices stating that it is 206 | released under this License and any conditions added under section 207 | 7. This requirement modifies the requirement in section 4 to 208 | "keep intact all notices". 209 | 210 | c) You must license the entire work, as a whole, under this 211 | License to anyone who comes into possession of a copy. This 212 | License will therefore apply, along with any applicable section 7 213 | additional terms, to the whole of the work, and all its parts, 214 | regardless of how they are packaged. This License gives no 215 | permission to license the work in any other way, but it does not 216 | invalidate such permission if you have separately received it. 217 | 218 | d) If the work has interactive user interfaces, each must display 219 | Appropriate Legal Notices; however, if the Program has interactive 220 | interfaces that do not display Appropriate Legal Notices, your 221 | work need not make them do so. 222 | 223 | A compilation of a covered work with other separate and independent 224 | works, which are not by their nature extensions of the covered work, 225 | and which are not combined with it such as to form a larger program, 226 | in or on a volume of a storage or distribution medium, is called an 227 | "aggregate" if the compilation and its resulting copyright are not 228 | used to limit the access or legal rights of the compilation's users 229 | beyond what the individual works permit. Inclusion of a covered work 230 | in an aggregate does not cause this License to apply to the other 231 | parts of the aggregate. 232 | 233 | 6. Conveying Non-Source Forms. 234 | 235 | You may convey a covered work in object code form under the terms 236 | of sections 4 and 5, provided that you also convey the 237 | machine-readable Corresponding Source under the terms of this License, 238 | in one of these ways: 239 | 240 | a) Convey the object code in, or embodied in, a physical product 241 | (including a physical distribution medium), accompanied by the 242 | Corresponding Source fixed on a durable physical medium 243 | customarily used for software interchange. 244 | 245 | b) Convey the object code in, or embodied in, a physical product 246 | (including a physical distribution medium), accompanied by a 247 | written offer, valid for at least three years and valid for as 248 | long as you offer spare parts or customer support for that product 249 | model, to give anyone who possesses the object code either (1) a 250 | copy of the Corresponding Source for all the software in the 251 | product that is covered by this License, on a durable physical 252 | medium customarily used for software interchange, for a price no 253 | more than your reasonable cost of physically performing this 254 | conveying of source, or (2) access to copy the 255 | Corresponding Source from a network server at no charge. 256 | 257 | c) Convey individual copies of the object code with a copy of the 258 | written offer to provide the Corresponding Source. This 259 | alternative is allowed only occasionally and noncommercially, and 260 | only if you received the object code with such an offer, in accord 261 | with subsection 6b. 262 | 263 | d) Convey the object code by offering access from a designated 264 | place (gratis or for a charge), and offer equivalent access to the 265 | Corresponding Source in the same way through the same place at no 266 | further charge. You need not require recipients to copy the 267 | Corresponding Source along with the object code. If the place to 268 | copy the object code is a network server, the Corresponding Source 269 | may be on a different server (operated by you or a third party) 270 | that supports equivalent copying facilities, provided you maintain 271 | clear directions next to the object code saying where to find the 272 | Corresponding Source. Regardless of what server hosts the 273 | Corresponding Source, you remain obligated to ensure that it is 274 | available for as long as needed to satisfy these requirements. 275 | 276 | e) Convey the object code using peer-to-peer transmission, provided 277 | you inform other peers where the object code and Corresponding 278 | Source of the work are being offered to the general public at no 279 | charge under subsection 6d. 280 | 281 | A separable portion of the object code, whose source code is excluded 282 | from the Corresponding Source as a System Library, need not be 283 | included in conveying the object code work. 284 | 285 | A "User Product" is either (1) a "consumer product", which means any 286 | tangible personal property which is normally used for personal, family, 287 | or household purposes, or (2) anything designed or sold for incorporation 288 | into a dwelling. In determining whether a product is a consumer product, 289 | doubtful cases shall be resolved in favor of coverage. For a particular 290 | product received by a particular user, "normally used" refers to a 291 | typical or common use of that class of product, regardless of the status 292 | of the particular user or of the way in which the particular user 293 | actually uses, or expects or is expected to use, the product. A product 294 | is a consumer product regardless of whether the product has substantial 295 | commercial, industrial or non-consumer uses, unless such uses represent 296 | the only significant mode of use of the product. 297 | 298 | "Installation Information" for a User Product means any methods, 299 | procedures, authorization keys, or other information required to install 300 | and execute modified versions of a covered work in that User Product from 301 | a modified version of its Corresponding Source. The information must 302 | suffice to ensure that the continued functioning of the modified object 303 | code is in no case prevented or interfered with solely because 304 | modification has been made. 305 | 306 | If you convey an object code work under this section in, or with, or 307 | specifically for use in, a User Product, and the conveying occurs as 308 | part of a transaction in which the right of possession and use of the 309 | User Product is transferred to the recipient in perpetuity or for a 310 | fixed term (regardless of how the transaction is characterized), the 311 | Corresponding Source conveyed under this section must be accompanied 312 | by the Installation Information. But this requirement does not apply 313 | if neither you nor any third party retains the ability to install 314 | modified object code on the User Product (for example, the work has 315 | been installed in ROM). 316 | 317 | The requirement to provide Installation Information does not include a 318 | requirement to continue to provide support service, warranty, or updates 319 | for a work that has been modified or installed by the recipient, or for 320 | the User Product in which it has been modified or installed. Access to a 321 | network may be denied when the modification itself materially and 322 | adversely affects the operation of the network or violates the rules and 323 | protocols for communication across the network. 324 | 325 | Corresponding Source conveyed, and Installation Information provided, 326 | in accord with this section must be in a format that is publicly 327 | documented (and with an implementation available to the public in 328 | source code form), and must require no special password or key for 329 | unpacking, reading or copying. 330 | 331 | 7. Additional Terms. 332 | 333 | "Additional permissions" are terms that supplement the terms of this 334 | License by making exceptions from one or more of its conditions. 335 | Additional permissions that are applicable to the entire Program shall 336 | be treated as though they were included in this License, to the extent 337 | that they are valid under applicable law. If additional permissions 338 | apply only to part of the Program, that part may be used separately 339 | under those permissions, but the entire Program remains governed by 340 | this License without regard to the additional permissions. 341 | 342 | When you convey a copy of a covered work, you may at your option 343 | remove any additional permissions from that copy, or from any part of 344 | it. (Additional permissions may be written to require their own 345 | removal in certain cases when you modify the work.) You may place 346 | additional permissions on material, added by you to a covered work, 347 | for which you have or can give appropriate copyright permission. 348 | 349 | Notwithstanding any other provision of this License, for material you 350 | add to a covered work, you may (if authorized by the copyright holders of 351 | that material) supplement the terms of this License with terms: 352 | 353 | a) Disclaiming warranty or limiting liability differently from the 354 | terms of sections 15 and 16 of this License; or 355 | 356 | b) Requiring preservation of specified reasonable legal notices or 357 | author attributions in that material or in the Appropriate Legal 358 | Notices displayed by works containing it; or 359 | 360 | c) Prohibiting misrepresentation of the origin of that material, or 361 | requiring that modified versions of such material be marked in 362 | reasonable ways as different from the original version; or 363 | 364 | d) Limiting the use for publicity purposes of names of licensors or 365 | authors of the material; or 366 | 367 | e) Declining to grant rights under trademark law for use of some 368 | trade names, trademarks, or service marks; or 369 | 370 | f) Requiring indemnification of licensors and authors of that 371 | material by anyone who conveys the material (or modified versions of 372 | it) with contractual assumptions of liability to the recipient, for 373 | any liability that these contractual assumptions directly impose on 374 | those licensors and authors. 375 | 376 | All other non-permissive additional terms are considered "further 377 | restrictions" within the meaning of section 10. If the Program as you 378 | received it, or any part of it, contains a notice stating that it is 379 | governed by this License along with a term that is a further 380 | restriction, you may remove that term. If a license document contains 381 | a further restriction but permits relicensing or conveying under this 382 | License, you may add to a covered work material governed by the terms 383 | of that license document, provided that the further restriction does 384 | not survive such relicensing or conveying. 385 | 386 | If you add terms to a covered work in accord with this section, you 387 | must place, in the relevant source files, a statement of the 388 | additional terms that apply to those files, or a notice indicating 389 | where to find the applicable terms. 390 | 391 | Additional terms, permissive or non-permissive, may be stated in the 392 | form of a separately written license, or stated as exceptions; 393 | the above requirements apply either way. 394 | 395 | 8. Termination. 396 | 397 | You may not propagate or modify a covered work except as expressly 398 | provided under this License. Any attempt otherwise to propagate or 399 | modify it is void, and will automatically terminate your rights under 400 | this License (including any patent licenses granted under the third 401 | paragraph of section 11). 402 | 403 | However, if you cease all violation of this License, then your 404 | license from a particular copyright holder is reinstated (a) 405 | provisionally, unless and until the copyright holder explicitly and 406 | finally terminates your license, and (b) permanently, if the copyright 407 | holder fails to notify you of the violation by some reasonable means 408 | prior to 60 days after the cessation. 409 | 410 | Moreover, your license from a particular copyright holder is 411 | reinstated permanently if the copyright holder notifies you of the 412 | violation by some reasonable means, this is the first time you have 413 | received notice of violation of this License (for any work) from that 414 | copyright holder, and you cure the violation prior to 30 days after 415 | your receipt of the notice. 416 | 417 | Termination of your rights under this section does not terminate the 418 | licenses of parties who have received copies or rights from you under 419 | this License. If your rights have been terminated and not permanently 420 | reinstated, you do not qualify to receive new licenses for the same 421 | material under section 10. 422 | 423 | 9. Acceptance Not Required for Having Copies. 424 | 425 | You are not required to accept this License in order to receive or 426 | run a copy of the Program. Ancillary propagation of a covered work 427 | occurring solely as a consequence of using peer-to-peer transmission 428 | to receive a copy likewise does not require acceptance. However, 429 | nothing other than this License grants you permission to propagate or 430 | modify any covered work. These actions infringe copyright if you do 431 | not accept this License. Therefore, by modifying or propagating a 432 | covered work, you indicate your acceptance of this License to do so. 433 | 434 | 10. Automatic Licensing of Downstream Recipients. 435 | 436 | Each time you convey a covered work, the recipient automatically 437 | receives a license from the original licensors, to run, modify and 438 | propagate that work, subject to this License. You are not responsible 439 | for enforcing compliance by third parties with this License. 440 | 441 | An "entity transaction" is a transaction transferring control of an 442 | organization, or substantially all assets of one, or subdividing an 443 | organization, or merging organizations. If propagation of a covered 444 | work results from an entity transaction, each party to that 445 | transaction who receives a copy of the work also receives whatever 446 | licenses to the work the party's predecessor in interest had or could 447 | give under the previous paragraph, plus a right to possession of the 448 | Corresponding Source of the work from the predecessor in interest, if 449 | the predecessor has it or can get it with reasonable efforts. 450 | 451 | You may not impose any further restrictions on the exercise of the 452 | rights granted or affirmed under this License. For example, you may 453 | not impose a license fee, royalty, or other charge for exercise of 454 | rights granted under this License, and you may not initiate litigation 455 | (including a cross-claim or counterclaim in a lawsuit) alleging that 456 | any patent claim is infringed by making, using, selling, offering for 457 | sale, or importing the Program or any portion of it. 458 | 459 | 11. Patents. 460 | 461 | A "contributor" is a copyright holder who authorizes use under this 462 | License of the Program or a work on which the Program is based. The 463 | work thus licensed is called the contributor's "contributor version". 464 | 465 | A contributor's "essential patent claims" are all patent claims 466 | owned or controlled by the contributor, whether already acquired or 467 | hereafter acquired, that would be infringed by some manner, permitted 468 | by this License, of making, using, or selling its contributor version, 469 | but do not include claims that would be infringed only as a 470 | consequence of further modification of the contributor version. For 471 | purposes of this definition, "control" includes the right to grant 472 | patent sublicenses in a manner consistent with the requirements of 473 | this License. 474 | 475 | Each contributor grants you a non-exclusive, worldwide, royalty-free 476 | patent license under the contributor's essential patent claims, to 477 | make, use, sell, offer for sale, import and otherwise run, modify and 478 | propagate the contents of its contributor version. 479 | 480 | In the following three paragraphs, a "patent license" is any express 481 | agreement or commitment, however denominated, not to enforce a patent 482 | (such as an express permission to practice a patent or covenant not to 483 | sue for patent infringement). To "grant" such a patent license to a 484 | party means to make such an agreement or commitment not to enforce a 485 | patent against the party. 486 | 487 | If you convey a covered work, knowingly relying on a patent license, 488 | and the Corresponding Source of the work is not available for anyone 489 | to copy, free of charge and under the terms of this License, through a 490 | publicly available network server or other readily accessible means, 491 | then you must either (1) cause the Corresponding Source to be so 492 | available, or (2) arrange to deprive yourself of the benefit of the 493 | patent license for this particular work, or (3) arrange, in a manner 494 | consistent with the requirements of this License, to extend the patent 495 | license to downstream recipients. "Knowingly relying" means you have 496 | actual knowledge that, but for the patent license, your conveying the 497 | covered work in a country, or your recipient's use of the covered work 498 | in a country, would infringe one or more identifiable patents in that 499 | country that you have reason to believe are valid. 500 | 501 | If, pursuant to or in connection with a single transaction or 502 | arrangement, you convey, or propagate by procuring conveyance of, a 503 | covered work, and grant a patent license to some of the parties 504 | receiving the covered work authorizing them to use, propagate, modify 505 | or convey a specific copy of the covered work, then the patent license 506 | you grant is automatically extended to all recipients of the covered 507 | work and works based on it. 508 | 509 | A patent license is "discriminatory" if it does not include within 510 | the scope of its coverage, prohibits the exercise of, or is 511 | conditioned on the non-exercise of one or more of the rights that are 512 | specifically granted under this License. You may not convey a covered 513 | work if you are a party to an arrangement with a third party that is 514 | in the business of distributing software, under which you make payment 515 | to the third party based on the extent of your activity of conveying 516 | the work, and under which the third party grants, to any of the 517 | parties who would receive the covered work from you, a discriminatory 518 | patent license (a) in connection with copies of the covered work 519 | conveyed by you (or copies made from those copies), or (b) primarily 520 | for and in connection with specific products or compilations that 521 | contain the covered work, unless you entered into that arrangement, 522 | or that patent license was granted, prior to 28 March 2007. 523 | 524 | Nothing in this License shall be construed as excluding or limiting 525 | any implied license or other defenses to infringement that may 526 | otherwise be available to you under applicable patent law. 527 | 528 | 12. No Surrender of Others' Freedom. 529 | 530 | If conditions are imposed on you (whether by court order, agreement or 531 | otherwise) that contradict the conditions of this License, they do not 532 | excuse you from the conditions of this License. If you cannot convey a 533 | covered work so as to satisfy simultaneously your obligations under this 534 | License and any other pertinent obligations, then as a consequence you may 535 | not convey it at all. For example, if you agree to terms that obligate you 536 | to collect a royalty for further conveying from those to whom you convey 537 | the Program, the only way you could satisfy both those terms and this 538 | License would be to refrain entirely from conveying the Program. 539 | 540 | 13. Remote Network Interaction; Use with the GNU General Public License. 541 | 542 | Notwithstanding any other provision of this License, if you modify the 543 | Program, your modified version must prominently offer all users 544 | interacting with it remotely through a computer network (if your version 545 | supports such interaction) an opportunity to receive the Corresponding 546 | Source of your version by providing access to the Corresponding Source 547 | from a network server at no charge, through some standard or customary 548 | means of facilitating copying of software. This Corresponding Source 549 | shall include the Corresponding Source for any work covered by version 3 550 | of the GNU General Public License that is incorporated pursuant to the 551 | following paragraph. 552 | 553 | Notwithstanding any other provision of this License, you have 554 | permission to link or combine any covered work with a work licensed 555 | under version 3 of the GNU General Public License into a single 556 | combined work, and to convey the resulting work. The terms of this 557 | License will continue to apply to the part which is the covered work, 558 | but the work with which it is combined will remain governed by version 559 | 3 of the GNU General Public License. 560 | 561 | 14. Revised Versions of this License. 562 | 563 | The Free Software Foundation may publish revised and/or new versions of 564 | the GNU Affero General Public License from time to time. Such new versions 565 | will be similar in spirit to the present version, but may differ in detail to 566 | address new problems or concerns. 567 | 568 | Each version is given a distinguishing version number. If the 569 | Program specifies that a certain numbered version of the GNU Affero General 570 | Public License "or any later version" applies to it, you have the 571 | option of following the terms and conditions either of that numbered 572 | version or of any later version published by the Free Software 573 | Foundation. If the Program does not specify a version number of the 574 | GNU Affero General Public License, you may choose any version ever published 575 | by the Free Software Foundation. 576 | 577 | If the Program specifies that a proxy can decide which future 578 | versions of the GNU Affero General Public License can be used, that proxy's 579 | public statement of acceptance of a version permanently authorizes you 580 | to choose that version for the Program. 581 | 582 | Later license versions may give you additional or different 583 | permissions. However, no additional obligations are imposed on any 584 | author or copyright holder as a result of your choosing to follow a 585 | later version. 586 | 587 | 15. Disclaimer of Warranty. 588 | 589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 597 | 598 | 16. Limitation of Liability. 599 | 600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 608 | SUCH DAMAGES. 609 | 610 | 17. Interpretation of Sections 15 and 16. 611 | 612 | If the disclaimer of warranty and limitation of liability provided 613 | above cannot be given local legal effect according to their terms, 614 | reviewing courts shall apply local law that most closely approximates 615 | an absolute waiver of all civil liability in connection with the 616 | Program, unless a warranty or assumption of liability accompanies a 617 | copy of the Program in return for a fee. 618 | 619 | END OF TERMS AND CONDITIONS 620 | 621 | How to Apply These Terms to Your New Programs 622 | 623 | If you develop a new program, and you want it to be of the greatest 624 | possible use to the public, the best way to achieve this is to make it 625 | free software which everyone can redistribute and change under these terms. 626 | 627 | To do so, attach the following notices to the program. It is safest 628 | to attach them to the start of each source file to most effectively 629 | state the exclusion of warranty; and each file should have at least 630 | the "copyright" line and a pointer to where the full notice is found. 631 | 632 | 633 | Copyright (C) 634 | 635 | This program is free software: you can redistribute it and/or modify 636 | it under the terms of the GNU Affero General Public License as published 637 | by the Free Software Foundation, either version 3 of the License, or 638 | (at your option) any later version. 639 | 640 | This program is distributed in the hope that it will be useful, 641 | but WITHOUT ANY WARRANTY; without even the implied warranty of 642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 643 | GNU Affero General Public License for more details. 644 | 645 | You should have received a copy of the GNU Affero General Public License 646 | along with this program. If not, see . 647 | 648 | Also add information on how to contact you by electronic and paper mail. 649 | 650 | If your software can interact with users remotely through a computer 651 | network, you should also make sure that it provides a way for users to 652 | get its source. For example, if your program is a web application, its 653 | interface could display a "Source" link that leads users to an archive 654 | of the code. There are many ways you could offer source, and different 655 | solutions will be better for different programs; see section 13 for the 656 | specific requirements. 657 | 658 | You should also get your employer (if you work as a programmer) or school, 659 | if any, to sign a "copyright disclaimer" for the program, if necessary. 660 | For more information on this, and how to apply and follow the GNU AGPL, see 661 | . 662 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # relay 2 | 3 | 高性能 relay(中继/转发)工具,类似 socat,但专注于 proxy 领域,通过组合不同的 in、out 协议,可实现: 4 | 5 | - ipt2socks:`in:tproxy` + `out:socks4/5` 6 | - tls-client:`in:tproxy` + `out:tlsproxy` 7 | - tls-server:`in:tlsproxy` + `out:raw` 8 | - trojan-tproxy:`in:tproxy` + `out:trojan` 9 | - trojan-client:`in:socks5` + `out:trojan` 10 | - trojan-server:`in:trojan` + `out:raw` 11 | 12 | > 积极开发中,优先实现 ipt2socks,并支持 socks4 传出,然后是 tlsproxy 协议、trojan-tproxy 客户端。 13 | 14 | # 设计目标 15 | 16 | - Linux only 17 | - 高性能,尽可能零拷贝,减少系统调用 18 | - 低资源开销,即便是低端路由器也能流畅运行 19 | - 支持条件编译,避免对不需要的协议支付相关成本 -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const builtin = @import("builtin"); 3 | const Builder = std.build.Builder; 4 | const CrossTarget = std.zig.CrossTarget; 5 | const BuildMode = std.builtin.Mode; 6 | const Step = std.build.Step; 7 | const LibExeObjStep = std.build.LibExeObjStep; 8 | const OptionsStep = std.build.OptionsStep; 9 | 10 | const app_name = "relay"; 11 | const app_version = "2024.05.20"; 12 | 13 | var _b: *Builder = undefined; 14 | 15 | // options 16 | var _test: bool = undefined; 17 | var _target: CrossTarget = undefined; 18 | var _mode: BuildMode = undefined; 19 | var _lto: bool = undefined; 20 | var _strip: bool = undefined; 21 | var _enable_all: bool = undefined; 22 | var _in_tproxy: bool = undefined; 23 | var _in_socks: bool = undefined; 24 | var _in_tlsproxy: bool = undefined; 25 | var _in_trojan: bool = undefined; 26 | var _out_raw: bool = undefined; 27 | var _out_socks: bool = undefined; 28 | var _out_tlsproxy: bool = undefined; 29 | var _out_trojan: bool = undefined; 30 | var _wolfssl: bool = undefined; 31 | var _wolfssl_noasm: bool = undefined; 32 | var _exe_name: []const u8 = undefined; 33 | 34 | // conditional compilation for zig source files 35 | var _build_opts: *OptionsStep = undefined; 36 | 37 | const DependLib = struct { 38 | url: []const u8, 39 | version: []const u8, 40 | tarball: []const u8, // src tarball file path 41 | src_dir: []const u8, 42 | src_dir_always_clean: bool, 43 | base_dir: []const u8, 44 | include_dir: []const u8, 45 | lib_dir: []const u8, 46 | }; 47 | 48 | var _dep_wolfssl: DependLib = b: { 49 | const version = "5.7.0"; 50 | const src_dir = "dep/wolfssl-" ++ version; 51 | break :b .{ 52 | .url = "https://github.com/wolfSSL/wolfssl/archive/refs/tags/v" ++ version ++ "-stable.tar.gz", 53 | .version = version, 54 | .tarball = src_dir ++ ".tar.gz", 55 | .src_dir = src_dir ++ "-stable", 56 | .src_dir_always_clean = false, 57 | .base_dir = undefined, // set by init() 58 | .include_dir = undefined, // set by init() 59 | .lib_dir = undefined, // set by init() 60 | }; 61 | }; 62 | 63 | fn init(b: *Builder) void { 64 | _b = b; 65 | 66 | if (_b.verbose) { 67 | _b.verbose_cimport = true; 68 | _b.verbose_llvm_cpu_features = true; 69 | _b.prominent_compile_errors = true; 70 | } 71 | 72 | // keep everything in a local directory 73 | _b.global_cache_root = _b.cache_root; 74 | 75 | // -Dxxx options 76 | option_test(); 77 | option_target(); 78 | option_mode(); 79 | option_lto(); 80 | option_strip(); 81 | option_all(); 82 | option_in_tproxy(); 83 | option_in_socks(); 84 | option_in_tlsproxy(); 85 | option_in_trojan(); 86 | option_out_raw(); 87 | option_out_socks(); 88 | option_out_tlsproxy(); 89 | option_out_trojan(); 90 | option_wolfssl(); 91 | option_wolfssl_noasm(); 92 | option_name(); // must be at the end 93 | 94 | // at least one protocol must be enabled 95 | if (!_in_tproxy and !_in_socks and !_in_tlsproxy and !_in_trojan) 96 | err_invalid("at least one in.proto must be enabled", .{}); 97 | if (!_out_raw and !_out_socks and !_out_tlsproxy and !_out_trojan) 98 | err_invalid("at least one out.proto must be enabled", .{}); 99 | 100 | _dep_wolfssl.base_dir = with_target_desc(_dep_wolfssl.src_dir, .ReleaseFast); // dependency lib always ReleaseFast 101 | if (_wolfssl_noasm) 102 | _dep_wolfssl.base_dir = fmt("{s}+noasm", .{_dep_wolfssl.base_dir}); 103 | _dep_wolfssl.include_dir = fmt("{s}/include", .{_dep_wolfssl.base_dir}); 104 | _dep_wolfssl.lib_dir = fmt("{s}/lib", .{_dep_wolfssl.base_dir}); 105 | 106 | // conditional compilation for zig source files 107 | _build_opts = _b.addOptions(); 108 | _build_opts.addOption(bool, "is_test", _test); 109 | _build_opts.addOption(bool, "in_tproxy", _in_tproxy); 110 | _build_opts.addOption(bool, "in_socks", _in_socks); 111 | _build_opts.addOption(bool, "in_tlsproxy", _in_tlsproxy); 112 | _build_opts.addOption(bool, "in_trojan", _in_trojan); 113 | _build_opts.addOption(bool, "out_raw", _out_raw); 114 | _build_opts.addOption(bool, "out_socks", _out_socks); 115 | _build_opts.addOption(bool, "out_tlsproxy", _out_tlsproxy); 116 | _build_opts.addOption(bool, "out_trojan", _out_trojan); 117 | _build_opts.addOption(bool, "wolfssl", _wolfssl); 118 | _build_opts.addOption([]const u8, "wolfssl_version", _dep_wolfssl.version); 119 | _build_opts.addOption([]const u8, "version", app_version); 120 | _build_opts.addOption([]const u8, "commit_id", get_commit_id()); 121 | _build_opts.addOption([]const u8, "target", desc_target()); 122 | _build_opts.addOption([]const u8, "cpu", desc_cpu()); 123 | _build_opts.addOption([]const u8, "mode", desc_mode(null)); 124 | 125 | // generate a zig source file (@import all zig source files of this project) 126 | gen_modules_zig(); 127 | } 128 | 129 | fn init_dep(step: *Step, dep: DependLib) void { 130 | if (dep.src_dir_always_clean and path_exists(dep.src_dir)) 131 | return; 132 | 133 | if (!path_exists(dep.tarball)) 134 | step.dependOn(download(dep.url, dep.tarball)); 135 | 136 | step.dependOn(rm(dep.src_dir)); 137 | 138 | step.dependOn(tar_extract(dep.tarball, "dep")); 139 | } 140 | 141 | // ========================================================================= 142 | 143 | fn option_test() void { 144 | _test = _b.option(bool, "test", "build artifacts for testing, default: false") orelse false; 145 | } 146 | 147 | fn option_target() void { 148 | _target = _b.standardTargetOptions(.{}); 149 | } 150 | 151 | fn option_mode() void { 152 | const default = if (_test) ModeOpt.debug else ModeOpt.fast; 153 | const opt = _b.option(ModeOpt, "mode", "build mode, default: 'fast' (or 'debug' if testing)") orelse default; 154 | _mode = to_mode(opt); 155 | } 156 | 157 | fn option_lto() void { 158 | const default = switch (_mode) { 159 | .ReleaseFast, .ReleaseSmall, .ReleaseSafe => true, 160 | else => false, 161 | }; 162 | _lto = _b.option(bool, "lto", "enable LTO, default to true if in fast/small/safe mode") orelse default; 163 | } 164 | 165 | fn option_strip() void { 166 | const default = switch (_mode) { 167 | .ReleaseFast, .ReleaseSmall => true, 168 | else => false, 169 | }; 170 | _strip = _b.option(bool, "strip", "strip debug info, default to true if in fast/small mode") orelse default; 171 | } 172 | 173 | fn option_all() void { 174 | _enable_all = _b.option(bool, "all", "enable all protocols by default, default: true") orelse true; 175 | } 176 | 177 | fn option_in_tproxy() void { 178 | _in_tproxy = _b.option(bool, "in.tproxy", "enable in.tproxy protocol") orelse _enable_all; 179 | } 180 | 181 | fn option_in_socks() void { 182 | _in_socks = _b.option(bool, "in.socks", "enable in.socks protocol") orelse _enable_all; 183 | } 184 | 185 | fn option_in_tlsproxy() void { 186 | _in_tlsproxy = _b.option(bool, "in.tlsproxy", "enable in.tlsproxy protocol") orelse _enable_all; 187 | } 188 | 189 | fn option_in_trojan() void { 190 | _in_trojan = _b.option(bool, "in.trojan", "enable in.trojan protocol") orelse _enable_all; 191 | } 192 | 193 | fn option_out_raw() void { 194 | _out_raw = _b.option(bool, "out.raw", "enable out.raw protocol") orelse _enable_all; 195 | } 196 | 197 | fn option_out_socks() void { 198 | _out_socks = _b.option(bool, "out.socks", "enable out.socks protocol") orelse _enable_all; 199 | } 200 | 201 | fn option_out_tlsproxy() void { 202 | _out_tlsproxy = _b.option(bool, "out.tlsproxy", "enable out.tlsproxy protocol") orelse _enable_all; 203 | } 204 | 205 | fn option_out_trojan() void { 206 | _out_trojan = _b.option(bool, "out.trojan", "enable out.trojan protocol") orelse _enable_all; 207 | } 208 | 209 | fn option_wolfssl() void { 210 | _wolfssl = _in_trojan or _out_tlsproxy or _out_trojan; 211 | } 212 | 213 | fn option_wolfssl_noasm() void { 214 | _wolfssl_noasm = _b.option(bool, "wolfssl-noasm", "disable the assembly acceleration option for wolfssl") orelse false; 215 | } 216 | 217 | fn option_name() void { 218 | var vec = std.ArrayList(u8).init(_b.allocator); 219 | defer vec.deinit(); 220 | 221 | if (_test) 222 | vec.appendSlice("test") catch unreachable 223 | else 224 | vec.appendSlice("relay") catch unreachable; 225 | 226 | const default = with_target_desc(vec.items, null); 227 | const desc = fmt("executable name, default: '{s}'", .{default}); 228 | 229 | const name = _b.option([]const u8, "name", desc) orelse default; 230 | const trimmed = trim_whitespace(name); 231 | 232 | if (trimmed.len > 0 and std.mem.eql(u8, trimmed, name)) { 233 | _exe_name = name; 234 | } else { 235 | err_invalid("invalid executable name (-Dname): '{s}'", .{name}); 236 | _exe_name = default; 237 | } 238 | } 239 | 240 | // ========================================================================= 241 | 242 | /// step: empty step to be used as a container 243 | fn no_op(name: []const u8) *Step { 244 | const step = _b.allocator.create(Step) catch unreachable; 245 | step.* = Step.initNoOp(.custom, name, _b.allocator); 246 | return step; 247 | } 248 | 249 | /// step: log info 250 | fn log(comptime format: []const u8, args: anytype) *Step { 251 | return &_b.addLog(format, args).step; 252 | } 253 | 254 | /// step: /bin/sh command 255 | fn sh(sh_cmd: []const u8) *Step { 256 | const cmd = fmt("set -o nounset; set -o errexit; {s}", .{sh_cmd}); 257 | const run_step = _b.addSystemCommand(&.{ "sh", "-c", cmd }); 258 | run_step.print = false; // disable print (use `set -x` instead) 259 | return &run_step.step; 260 | } 261 | 262 | /// step: /bin/sh command (set -x) 263 | fn sh_x(sh_cmd: []const u8) *Step { 264 | return sh(fmt("set -x; {s}", .{sh_cmd})); 265 | } 266 | 267 | /// step: remove dir or file 268 | fn rm(path: []const u8) *Step { 269 | return &_b.addRemoveDirTree(path).step; 270 | } 271 | 272 | /// step: download file 273 | fn download(url: []const u8, path: []const u8) *Step { 274 | const cmd_ = 275 | \\ url='{s}'; path='{s}' 276 | \\ mkdir -p "$(dirname "$path")" 277 | \\ echo "[INFO] downloading from $url" 278 | \\ if command -v wget >/dev/null; then 279 | \\ wget "$url" -O "$path" 280 | \\ elif command -v curl >/dev/null; then 281 | \\ curl -fL "$url" -o "$path" 282 | \\ else 283 | \\ echo "[ERROR] please install 'wget' or 'curl'" 1>&2 284 | \\ exit 1 285 | \\ fi 286 | ; 287 | const cmd = fmt(cmd_, .{ url, path }); 288 | return sh(cmd); 289 | } 290 | 291 | /// step: tar -xf $tarball -C $dir 292 | fn tar_extract(tarball_path: []const u8, to_dir: []const u8) *Step { 293 | const cmd = fmt("mkdir -p '{s}'; tar -xf '{s}' -C '{s}'", .{ to_dir, tarball_path, to_dir }); 294 | return sh_x(cmd); 295 | } 296 | 297 | // ========================================================================= 298 | 299 | var _first_error: bool = true; 300 | 301 | /// print to stderr, auto append '\n' 302 | fn print(comptime format: []const u8, args: anytype) void { 303 | _ = std.io.getStdErr().write(fmt(format ++ "\n", args)) catch unreachable; 304 | } 305 | 306 | /// print("") 307 | fn newline() void { 308 | return print("", .{}); 309 | } 310 | 311 | /// print("> ERROR: msg") 312 | fn print_err(comptime format: []const u8, args: anytype) void { 313 | if (_first_error) { 314 | _first_error = false; 315 | newline(); 316 | } 317 | print("> ERROR: " ++ format, args); 318 | } 319 | 320 | /// print("> ERROR: msg") && mark user_input as invalid 321 | fn err_invalid(comptime format: []const u8, args: anytype) void { 322 | print_err(format, args); 323 | _b.invalid_user_input = true; 324 | } 325 | 326 | /// print("> ERROR: msg") && std.os.exit(1) 327 | fn err_exit(comptime format: []const u8, args: anytype) noreturn { 328 | print_err(format, args); 329 | newline(); 330 | std.os.exit(1); 331 | } 332 | 333 | // ========================================================================= 334 | 335 | fn dupe(bytes: []const u8) []u8 { 336 | return _b.dupe(bytes); 337 | } 338 | 339 | fn fmt(comptime format: []const u8, args: anytype) []const u8 { 340 | return _b.fmt(format, args); 341 | } 342 | 343 | fn path_exists(rel_path: []const u8) bool { 344 | return if (std.fs.cwd().access(rel_path, .{})) true else |_| false; 345 | } 346 | 347 | /// caller owns the returned memory `_b.allocator.free(mem)` 348 | fn string_concat(str_list: []const []const u8, sep: []const u8) []const u8 { 349 | return std.mem.join(_b.allocator, sep, str_list) catch unreachable; 350 | } 351 | 352 | fn trim_whitespace(str: []const u8) []const u8 { 353 | return std.mem.trim(u8, str, " \t\r\n"); 354 | } 355 | 356 | /// caller owns the returned stdout `_b.allocator.free(mem)` 357 | fn exec_command(argv: []const []const u8, exit_code: ?*u8) Builder.ExecError![]u8 { 358 | var code: u8 = undefined; 359 | const p_code = exit_code orelse &code; 360 | return _b.execAllowFail(argv, p_code, .Inherit) catch |err| { 361 | const cmd = string_concat(argv, " "); 362 | defer _b.allocator.free(cmd); 363 | print_err("failed to execute: {s} ({s} exit_code:{d})", .{ cmd, @errorName(err), p_code.* }); 364 | return err; 365 | }; 366 | } 367 | 368 | /// git commit id (HEAD) 369 | fn get_commit_id() []const u8 { 370 | const str = exec_command(&.{ "git", "rev-parse", "--short", "HEAD" }, null) catch "unknown"; 371 | return trim_whitespace(str); 372 | } 373 | 374 | // ========================================================================= 375 | 376 | const ModeOpt = enum { fast, small, safe, debug }; 377 | 378 | fn to_mode(opt: ModeOpt) BuildMode { 379 | return switch (opt) { 380 | .fast => .ReleaseFast, 381 | .small => .ReleaseSmall, 382 | .safe => .ReleaseSafe, 383 | .debug => .Debug, 384 | }; 385 | } 386 | 387 | fn to_mode_opt(mode: BuildMode) ModeOpt { 388 | return switch (mode) { 389 | .ReleaseFast => .fast, 390 | .ReleaseSmall => .small, 391 | .ReleaseSafe => .safe, 392 | .Debug => .debug, 393 | }; 394 | } 395 | 396 | // ========================================================================= 397 | 398 | fn is_musl() bool { 399 | return _target.getAbi().isMusl(); 400 | } 401 | 402 | /// return 0 if not x86_64 arch 403 | fn get_x86_64_level() u8 { 404 | const name = _target.getCpuModel().name; 405 | if (!std.mem.startsWith(u8, name, "x86_64")) 406 | return 0; 407 | if (std.mem.eql(u8, name, "x86_64")) 408 | return 1; 409 | if (std.mem.eql(u8, name, "x86_64_v2")) 410 | return 2; 411 | if (std.mem.eql(u8, name, "x86_64_v3")) 412 | return 3; 413 | if (std.mem.eql(u8, name, "x86_64_v4")) 414 | return 4; 415 | unreachable; 416 | } 417 | 418 | /// get cli option value (string) 419 | fn get_optval(name: []const u8) ?[]const u8 { 420 | const opt = _b.user_input_options.getPtr(name) orelse return null; 421 | return switch (opt.value) { 422 | .scalar => |v| v, 423 | else => null, 424 | }; 425 | } 426 | 427 | fn get_optval_target() ?[]const u8 { 428 | return get_optval("target"); 429 | } 430 | 431 | fn get_optval_cpu() ?[]const u8 { 432 | return get_optval("cpu"); 433 | } 434 | 435 | /// for zig cc (build wolfssl) 436 | fn get_target_mcpu() []const u8 { 437 | const target = get_optval_target() orelse "native"; 438 | return if (get_optval_cpu()) |cpu| 439 | fmt("-target {s} -mcpu={s}", .{ target, cpu }) 440 | else 441 | fmt("-target {s}", .{target}); 442 | } 443 | 444 | /// caller owns the returned stdout `_b.allocator.free(mem)` 445 | fn show_builtin() []const u8 { 446 | var argv = std.ArrayList([]const u8).init(_b.allocator); 447 | defer argv.deinit(); 448 | 449 | argv.appendSlice(&.{ _b.zig_exe, "build-exe" }) catch unreachable; 450 | 451 | if (get_optval_target()) |target| 452 | argv.appendSlice(&.{ "-target", target }) catch unreachable; 453 | 454 | if (get_optval_cpu()) |cpu| 455 | argv.append(fmt("-mcpu={s}", .{cpu})) catch unreachable; 456 | 457 | argv.append("--show-builtin") catch unreachable; 458 | 459 | return exec_command(argv.items, null) catch unreachable; 460 | } 461 | 462 | fn get_glibc_version() std.builtin.Version { 463 | const builtin_info = show_builtin(); 464 | defer _b.allocator.free(builtin_info); 465 | 466 | var stream = std.io.fixedBufferStream(builtin_info); 467 | const reader = stream.reader(); 468 | 469 | var buffer: [512]u8 = undefined; 470 | 471 | while (reader.readUntilDelimiterOrEof(&buffer, '\n') catch unreachable) |raw_line| { 472 | const line = trim_whitespace(raw_line); 473 | 474 | if (std.mem.eql(u8, line, ".glibc = .{")) { 475 | // .glibc = .{ 476 | // .major = 2, 477 | // .minor = 38, 478 | // .patch = 0, 479 | // }, 480 | 481 | var major_buf: [100]u8 = undefined; 482 | var minor_buf: [100]u8 = undefined; 483 | var patch_buf: [100]u8 = undefined; 484 | 485 | const major_line = trim_whitespace((reader.readUntilDelimiterOrEof(&major_buf, '\n') catch unreachable).?); 486 | const minor_line = trim_whitespace((reader.readUntilDelimiterOrEof(&minor_buf, '\n') catch unreachable).?); 487 | const patch_line = trim_whitespace((reader.readUntilDelimiterOrEof(&patch_buf, '\n') catch unreachable).?); 488 | 489 | if (!std.mem.startsWith(u8, major_line, ".major = ")) 490 | err_exit("failed to get glibc version for given target: invalid major line: {s}", .{major_line}); 491 | 492 | if (!std.mem.startsWith(u8, minor_line, ".minor = ")) 493 | err_exit("failed to get glibc version for given target: invalid minor line: {s}", .{minor_line}); 494 | 495 | if (!std.mem.eql(u8, patch_line, ".patch = 0,")) 496 | err_exit("failed to get glibc version for given target: invalid patch line: {s}", .{patch_line}); 497 | 498 | return .{ 499 | .major = std.fmt.parseInt(u32, major_line[9 .. major_line.len - 1], 10) catch unreachable, 500 | .minor = std.fmt.parseInt(u32, minor_line[9 .. minor_line.len - 1], 10) catch unreachable, 501 | }; 502 | } 503 | } 504 | 505 | err_exit("failed to get glibc version for given target: invalid format", .{}); 506 | } 507 | 508 | fn desc_target() []const u8 { 509 | const cpu_arch = _target.getCpuArch(); 510 | const os_tag = _target.getOsTag(); 511 | const abi = _target.getAbi(); 512 | 513 | if (_target.isGnuLibC()) { 514 | const glibc_version = _target.glibc_version orelse get_glibc_version(); 515 | return fmt("{s}-{s}-{s}.{}", .{ @tagName(cpu_arch), @tagName(os_tag), @tagName(abi), glibc_version }); 516 | } else { 517 | return fmt("{s}-{s}-{s}", .{ @tagName(cpu_arch), @tagName(os_tag), @tagName(abi) }); 518 | } 519 | } 520 | 521 | fn desc_cpu() []const u8 { 522 | if (get_optval_cpu()) |cpu| return cpu; 523 | const cpu_model = _target.getCpuModel().name; 524 | return if (_target.isNativeCpu()) fmt("{s}+native", .{cpu_model}) else cpu_model; 525 | } 526 | 527 | /// @mode: default is `_mode` 528 | /// {fast | small | safe | debug} [+lto] 529 | fn desc_mode(mode: ?BuildMode) []const u8 { 530 | const res = @tagName(to_mode_opt(mode orelse _mode)); 531 | return if (_lto) fmt("{s}+lto", .{res}) else res; 532 | } 533 | 534 | /// @in_mode: default is `_mode` 535 | fn with_target_desc(name: []const u8, in_mode: ?BuildMode) []const u8 { 536 | const target = desc_target(); 537 | const cpu = desc_cpu(); 538 | const mode = desc_mode(in_mode orelse _mode); 539 | return fmt("{s}@{s}@{s}@{s}", .{ name, target, cpu, mode }); 540 | } 541 | 542 | fn gen_modules_zig() void { 543 | var f = std.fs.cwd().createFile("src/modules.zig", .{}) catch unreachable; 544 | defer f.close(); 545 | 546 | var dir = std.fs.cwd().openIterableDir("src", .{}) catch unreachable; 547 | defer dir.close(); 548 | 549 | var list = std.ArrayList([]const u8).init(_b.allocator); 550 | defer list.deinit(); 551 | 552 | var it = dir.iterate(); 553 | while (it.next() catch unreachable) |file| { 554 | if (file.kind != .File) 555 | continue; 556 | if (!std.mem.endsWith(u8, file.name, ".zig")) 557 | continue; 558 | list.append(dupe(file.name[0 .. file.name.len - 4])) catch unreachable; 559 | } 560 | 561 | std.sort.sort([]const u8, list.items, {}, struct { 562 | fn cmp(_: void, a: []const u8, b: []const u8) bool { 563 | return std.mem.order(u8, a, b).compare(.lt); 564 | } 565 | }.cmp); 566 | 567 | var text = std.ArrayList(u8).init(_b.allocator); 568 | defer text.deinit(); 569 | 570 | text.appendSlice("pub const name_list = .{ ") catch unreachable; 571 | var i: usize = 0; // make zls 0.12 happy 572 | for (list.items) |name| { 573 | defer i += 1; 574 | if (i > 0) 575 | text.appendSlice(", ") catch unreachable; 576 | text.append('"') catch unreachable; 577 | text.appendSlice(name) catch unreachable; 578 | text.append('"') catch unreachable; 579 | } 580 | text.appendSlice(" };\n") catch unreachable; 581 | 582 | text.appendSlice("pub const module_list = .{ ") catch unreachable; 583 | i = 0; 584 | for (list.items) |name| { 585 | defer i += 1; 586 | if (i > 0) 587 | text.appendSlice(", ") catch unreachable; 588 | text.appendSlice(name) catch unreachable; 589 | } 590 | text.appendSlice(" };\n\n") catch unreachable; 591 | 592 | for (list.items) |name| { 593 | text.appendSlice("const ") catch unreachable; 594 | text.appendSlice(name) catch unreachable; 595 | text.appendSlice(" = @import(\"") catch unreachable; 596 | text.appendSlice(name) catch unreachable; 597 | text.appendSlice(".zig\");\n") catch unreachable; 598 | } 599 | 600 | f.writeAll(text.items) catch unreachable; 601 | } 602 | 603 | // ========================================================================= 604 | 605 | fn new_exe(name: []const u8) *LibExeObjStep { 606 | const exe = _b.addExecutable(name, null); 607 | setup_artifact(exe); 608 | return exe; 609 | } 610 | 611 | fn new_obj(name: []const u8, root_src: ?[]const u8) *LibExeObjStep { 612 | const obj = _b.addObject(name, root_src); 613 | setup_artifact(obj); 614 | return obj; 615 | } 616 | 617 | fn setup_artifact(step: *LibExeObjStep) void { 618 | step.setTarget(_target); 619 | step.setBuildMode(_mode); 620 | 621 | step.want_lto = _lto; 622 | step.strip = _strip; 623 | 624 | // compile 625 | if (step.kind == .obj) 626 | step.use_stage1 = true; // required by async/await (.zig) 627 | 628 | step.single_threaded = true; 629 | 630 | step.link_function_sections = true; 631 | // step.link_data_sections = true; // not supported in 0.10.1 632 | 633 | // link 634 | if (step.kind == .exe or step.isDynamicLibrary()) 635 | step.link_gc_sections = true; 636 | 637 | step.pie = false; 638 | 639 | if (is_musl()) 640 | step.force_pic = false; 641 | 642 | // this is needed even for the compile step, as zig needs to do some preparation for linking libc 643 | step.linkLibC(); 644 | } 645 | 646 | /// zig build-obj -cflags 647 | fn get_cflags(ex_cflags: []const []const u8) []const []const u8 { 648 | var cflags = std.ArrayList([]const u8).init(_b.allocator); 649 | defer cflags.deinit(); 650 | 651 | cflags.appendSlice(&.{ 652 | "-Werror", // https://github.com/ziglang/zig/issues/10800 653 | "-fno-pic", 654 | "-fno-PIC", 655 | "-fno-pie", 656 | "-fno-PIE", 657 | "-ffunction-sections", 658 | "-fdata-sections", 659 | "-fcolor-diagnostics", 660 | "-fcaret-diagnostics", 661 | }) catch unreachable; 662 | 663 | if (_mode == .ReleaseFast) 664 | cflags.append("-O3") catch unreachable; // default is -O2 665 | 666 | // append ex cflags 667 | cflags.appendSlice(ex_cflags) catch unreachable; 668 | 669 | return cflags.toOwnedSlice(); 670 | } 671 | 672 | // ========================================================================= 673 | 674 | fn build_wolfssl() *Step { 675 | const wolfssl = no_op("wolfssl"); 676 | 677 | // already installed ? 678 | if (path_exists(_dep_wolfssl.base_dir)) 679 | return wolfssl; 680 | 681 | init_dep(wolfssl, _dep_wolfssl); 682 | 683 | const cmd_ = 684 | \\ install_dir='{s}' 685 | \\ src_dir='{s}' 686 | \\ zig_exe='{s}' 687 | \\ target_mcpu='{s}' 688 | \\ target_triple='{s}' 689 | \\ zig_cache_dir='{s}' 690 | \\ is_musl='{s}' 691 | \\ lto='{s}' 692 | \\ aesni='{s}' 693 | \\ intelasm='{s}' 694 | \\ armasm='{s}' 695 | \\ aarch64='{s}' 696 | \\ cwd="$PWD" 697 | \\ 698 | \\ cd "$src_dir" 699 | \\ 700 | \\ export ZIG_LOCAL_CACHE_DIR="$zig_cache_dir" 701 | \\ export ZIG_GLOBAL_CACHE_DIR="$zig_cache_dir" 702 | \\ 703 | \\ [ "$is_musl" = 1 ] && pic_flags='-fno-pic -fno-PIC' || pic_flags='' 704 | \\ export CC="$zig_exe cc $target_mcpu -g0 -O3 -Xclang -O3 $lto -fno-pie -fno-PIE $pic_flags -ffunction-sections -fdata-sections" 705 | \\ export AR="$zig_exe ar" 706 | \\ export RANLIB="$zig_exe ranlib" 707 | \\ 708 | \\ [ "$target_triple" ] && host="--host=$target_triple" || host="" 709 | \\ [ "$aarch64" = 1 ] && opt_sha512="--enable-sha512" || opt_sha512="--disable-sha512" 710 | \\ 711 | \\ ./autogen.sh 712 | \\ ./configure \ 713 | \\ $host \ 714 | \\ $aesni \ 715 | \\ $intelasm \ 716 | \\ $armasm \ 717 | \\ --prefix="$install_dir" \ 718 | \\ --enable-static \ 719 | \\ --disable-shared \ 720 | \\ --enable-asm \ 721 | \\ --disable-harden \ 722 | \\ --disable-ocsp \ 723 | \\ --disable-oldnames \ 724 | \\ --enable-sys-ca-certs \ 725 | \\ --disable-memory \ 726 | \\ --disable-staticmemory \ 727 | \\ --enable-singlethreaded \ 728 | \\ --disable-threadlocal \ 729 | \\ --disable-asyncthreads \ 730 | \\ --disable-errorqueue \ 731 | \\ --disable-error-queue-per-thread \ 732 | \\ --disable-openssl-compatible-defaults \ 733 | \\ --disable-opensslextra \ 734 | \\ --disable-opensslall \ 735 | \\ --disable-dtls \ 736 | \\ --disable-oldtls \ 737 | \\ --enable-tls13 \ 738 | \\ --enable-chacha \ 739 | \\ --enable-poly1305 \ 740 | \\ --enable-aesgcm \ 741 | \\ --disable-aescbc \ 742 | \\ --enable-sni \ 743 | \\ --enable-session-ticket \ 744 | \\ --disable-md5 \ 745 | \\ --disable-sha \ 746 | \\ --disable-sha3 \ 747 | \\ --disable-sha224 \ 748 | \\ $opt_sha512 \ 749 | \\ --disable-pkcs7 \ 750 | \\ --disable-pkcs8 \ 751 | \\ --disable-pkcs11 \ 752 | \\ --disable-pkcs12 \ 753 | \\ --disable-dh \ 754 | \\ --enable-ecc \ 755 | \\ --enable-rsa \ 756 | \\ --disable-oaep \ 757 | \\ --enable-coding \ 758 | \\ --disable-base64encode \ 759 | \\ --disable-asn-print \ 760 | \\ --disable-pwdbased \ 761 | \\ --disable-secure-renegotiation-info \ 762 | \\ --disable-crypttests \ 763 | \\ --disable-benchmark \ 764 | \\ --disable-examples \ 765 | \\ EXTRA_CFLAGS="-include $cwd/src/wolfssl_opt.h" 766 | \\ make install 767 | ; 768 | 769 | const opt_musl: [:0]const u8 = if (is_musl()) "1" else "0"; 770 | const opt_lto: [:0]const u8 = if (_lto) "-flto" else ""; 771 | const opt_aesni: [:0]const u8 = if (_target.getCpuArch() == .x86_64) "--enable-aesni" else ""; 772 | const opt_intelasm: [:0]const u8 = if (!_wolfssl_noasm and get_x86_64_level() >= 3) "--enable-intelasm" else ""; 773 | const opt_armasm: [:0]const u8 = if (!_wolfssl_noasm and _target.getCpuArch() == .aarch64) "--enable-armasm" else ""; 774 | const opt_aarch64: [:0]const u8 = if (_target.getCpuArch() == .aarch64) "1" else "0"; 775 | 776 | const cmd = fmt(cmd_, .{ 777 | _b.pathFromRoot(_dep_wolfssl.base_dir), 778 | _dep_wolfssl.src_dir, 779 | _b.zig_exe, 780 | get_target_mcpu(), 781 | get_optval_target() orelse "", 782 | _b.pathFromRoot(_b.cache_root), 783 | opt_musl, 784 | opt_lto, 785 | opt_aesni, 786 | opt_intelasm, 787 | opt_armasm, 788 | opt_aarch64, 789 | }); 790 | 791 | wolfssl.dependOn(sh_x(cmd)); 792 | 793 | return wolfssl; 794 | } 795 | 796 | fn add_app_objs(app_exe: *LibExeObjStep) void { 797 | const cflags = get_cflags(&.{ 798 | "-std=c99", 799 | "-Wall", 800 | "-Wextra", 801 | "-Wvla", 802 | }); 803 | 804 | var dir = std.fs.cwd().openIterableDir("src", .{}) catch unreachable; 805 | defer dir.close(); 806 | 807 | var it = dir.iterate(); 808 | 809 | while (it.next() catch unreachable) |file| { 810 | if (file.kind != .File) 811 | continue; 812 | 813 | const is_root_zig = std.mem.eql(u8, file.name, "main.zig"); 814 | const is_c_file = std.mem.endsWith(u8, file.name, ".c"); 815 | 816 | if (!is_root_zig and !is_c_file) 817 | continue; 818 | 819 | const filepath = fmt("src/{s}", .{file.name}); 820 | 821 | const obj = new_obj(file.name, if (is_root_zig) filepath else null); 822 | 823 | if (is_root_zig) { 824 | obj.addIncludePath("."); // used to @cInclude("src/*.h") 825 | obj.addOptions("build_opts", _build_opts); // for conditional compilation 826 | } else { 827 | obj.addCSourceFile(filepath, cflags); 828 | } 829 | 830 | obj.defineCMacroRaw(fmt("LOG_FILENAME=\"{s}\"", .{file.name})); 831 | 832 | if (_test) 833 | obj.defineCMacroRaw("TEST"); 834 | 835 | if (is_musl()) 836 | obj.defineCMacroRaw("MUSL"); 837 | 838 | if (_wolfssl) { 839 | obj.defineCMacroRaw("ENABLE_WOLFSSL"); 840 | obj.addIncludePath(_dep_wolfssl.include_dir); 841 | } 842 | 843 | app_exe.addObject(obj); 844 | } 845 | } 846 | 847 | // ========================================================================= 848 | 849 | fn configure() void { 850 | const exe = new_exe(_exe_name); 851 | 852 | // build the dependency library first 853 | if (_wolfssl) 854 | exe.step.dependOn(build_wolfssl()); 855 | 856 | // src/main.zig, src/*.c 857 | add_app_objs(exe); 858 | 859 | // link the dependency library 860 | if (_wolfssl) { 861 | exe.addLibraryPath(_dep_wolfssl.lib_dir); 862 | exe.linkSystemLibrary("wolfssl"); 863 | } 864 | 865 | // install to dest dir 866 | exe.install(); 867 | 868 | const run_exe = exe.run(); 869 | if (_b.args) |args| 870 | run_exe.addArgs(args); 871 | 872 | // zig build run [-- ARGS...] 873 | const run = _b.step("run", "run the executable: [-- ARGS...]"); 874 | run.dependOn(_b.getInstallStep()); 875 | run.dependOn(&run_exe.step); 876 | 877 | const rm_cache = rm(_b.cache_root); 878 | const rm_wolfssl = rm(_dep_wolfssl.base_dir); // current target 879 | const rm_wolfssl_all = sh(fmt("rm -fr {s}@*", .{_dep_wolfssl.src_dir})); // all targets 880 | 881 | // zig build clean-cache 882 | const clean_cache = _b.step("clean-cache", fmt("clean zig build cache: '{s}'", .{_b.cache_root})); 883 | clean_cache.dependOn(rm_cache); 884 | 885 | // zig build clean-wolfssl 886 | const clean_wolfssl = _b.step("clean-wolfssl", fmt("clean wolfssl build cache: '{s}'", .{_dep_wolfssl.base_dir})); 887 | clean_wolfssl.dependOn(rm_wolfssl); 888 | 889 | // zig build clean-wolfssl-all 890 | const clean_wolfssl_all = _b.step("clean-wolfssl-all", fmt("clean wolfssl build caches: '{s}@*'", .{_dep_wolfssl.src_dir})); 891 | clean_wolfssl_all.dependOn(rm_wolfssl_all); 892 | 893 | // zig build clean 894 | const clean = _b.step("clean", fmt("clean all build caches", .{})); 895 | clean.dependOn(clean_cache); 896 | clean.dependOn(clean_wolfssl); 897 | 898 | // zig build clean-all 899 | const clean_all = _b.step("clean-all", fmt("clean all build caches (*)", .{})); 900 | clean_all.dependOn(clean_cache); 901 | clean_all.dependOn(clean_wolfssl_all); 902 | } 903 | 904 | /// build.zig just generates the build steps (and the dependency graph), the real running is done by build_runner.zig 905 | pub fn build(b: *Builder) void { 906 | init(b); 907 | 908 | configure(); 909 | 910 | if (_b.invalid_user_input) 911 | newline(); 912 | } 913 | -------------------------------------------------------------------------------- /relay.conf: -------------------------------------------------------------------------------- 1 | [global] 2 | verbose = true 3 | threads = 0 # 0 means $nproc 4 | cert_verify = true 5 | ca_certs = /etc/ssl/cert.pem 6 | 7 | [in.tproxy] 8 | ip = 127.0.0.1 9 | ip = ::1 10 | port = 60080 11 | 12 | # [in.socks] 13 | # ip = 0.0.0.0 14 | # port = 1080 15 | # passwd = user1 passwd1 16 | # passwd = user2 passwd2 17 | # passwd = user3 passwd3 18 | 19 | # # access via nginx reverse proxy 20 | # # tls-client -> nginx:443 -> tls-server 21 | # [in.tlsproxy] 22 | # ip = 127.0.0.1 23 | # port = 60080 24 | 25 | # # not supported yet 26 | # [in.trojan] 27 | # ip = 0.0.0.0 28 | # port = 443 29 | # passwd = passwd1 passwd2 passwd3 30 | 31 | # [out.raw] 32 | # # no config is required 33 | 34 | # [out.socks] 35 | # server = 192.168.1.3 36 | # port = 1080 37 | # passwd = user1 passwd1 38 | 39 | [out.tlsproxy] 40 | server = tlsproxy.vps.com 41 | passwd = passwd1 42 | 43 | # [out.trojan] 44 | # server = trojan.vps.com 45 | # passwd = passwd1 46 | -------------------------------------------------------------------------------- /src/Config.zig: -------------------------------------------------------------------------------- 1 | const cfg_loader = @import("cfg_loader.zig"); 2 | const cfg_checker = @import("cfg_checker.zig"); 3 | 4 | const Config = @This(); 5 | 6 | ca_certs: [:0]const u8 = "", 7 | cert_verify: bool = false, 8 | reuse_port: bool = false, 9 | verbose: bool = false, 10 | threads: u8 = 1, 11 | 12 | pub fn load(content: []const u8) ?Config { 13 | var self = Config{}; 14 | const src = @src(); 15 | cfg_loader.load(src, &self, content) orelse return null; 16 | return self; 17 | } 18 | -------------------------------------------------------------------------------- /src/DynStr.zig: -------------------------------------------------------------------------------- 1 | const cc = @import("cc.zig"); 2 | const SentinelVector = @import("sentinel_vector.zig").SentinelVector; 3 | 4 | // ========================================== 5 | 6 | const DynStr = @This(); 7 | 8 | vec: SentinelVector(u8, 0) = .{}, 9 | 10 | // ========================================== 11 | 12 | /// copy string to buffer 13 | pub fn set(self: *DynStr, str: []const u8) void { 14 | return self.set_x(&.{str}); 15 | } 16 | 17 | /// copy strings to buffer 18 | pub fn set_x(self: *DynStr, str_list: []const []const u8) void { 19 | var strlen: usize = 0; 20 | for (str_list) |str| 21 | strlen += str.len; 22 | 23 | self.vec.resize(strlen); 24 | 25 | var ptr = self.vec.items.ptr; 26 | for (str_list) |str| { 27 | @memcpy(ptr, str.ptr, str.len); 28 | ptr += str.len; 29 | } 30 | } 31 | 32 | pub fn slice(self: *const DynStr) [:0]const u8 { 33 | return self.vec.items; 34 | } 35 | 36 | pub fn cstr(self: *const DynStr) cc.ConstStr { 37 | return self.slice().ptr; 38 | } 39 | 40 | pub fn is_null(self: *const DynStr) bool { 41 | return self.vec.is_null(); 42 | } 43 | 44 | pub fn is_empty(self: *const DynStr) bool { 45 | return self.vec.is_empty(); 46 | } 47 | -------------------------------------------------------------------------------- /src/EvLoop.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const root = @import("root"); 3 | const build_opts = @import("build_opts"); 4 | const g = @import("g.zig"); 5 | const c = @import("c.zig"); 6 | const cc = @import("cc.zig"); 7 | const co = @import("co.zig"); 8 | const log = @import("log.zig"); 9 | const net = @import("net.zig"); 10 | const Rc = @import("Rc.zig"); 11 | const assert = std.debug.assert; 12 | const Ptr = cc.Ptr; 13 | 14 | // ============================================================= 15 | 16 | const EvLoop = @This(); 17 | 18 | /// avoid touching freed ptr, see evloop.run() 19 | destroyed: std.AutoHashMapUnmanaged(*const Fd, void) = .{}, 20 | 21 | /// cache fd's add/del operation (reducing epoll_ctl calls) 22 | change_list: std.AutoHashMapUnmanaged(*const Fd, Change.Set) = .{}, 23 | 24 | /// epoll instance (fd) 25 | epfd: c_int, 26 | 27 | // ============================================================= 28 | 29 | const Change = opaque { 30 | pub const T = u8; 31 | 32 | pub const ADD_READ: T = 1 << 0; 33 | pub const DEL_READ: T = 1 << 1; 34 | pub const ADD_WRITE: T = 1 << 2; 35 | pub const DEL_WRITE: T = 1 << 3; 36 | 37 | /// wrapper for T 38 | pub const Set = struct { 39 | set: T = 0, 40 | 41 | pub fn is_empty(self: Set) bool { 42 | return self.set == 0; 43 | } 44 | 45 | /// has all 46 | pub fn has(self: Set, v: T) bool { 47 | return self.set & v == v; 48 | } 49 | 50 | pub fn has_any(self: Set, v: T) bool { 51 | return self.set & v != 0; 52 | } 53 | 54 | pub fn del(self: *Set, v: T) void { 55 | self.set &= ~v; 56 | } 57 | 58 | pub fn add(self: *Set, v: T) void { 59 | self.set |= v; 60 | 61 | if (self.has(ADD_READ | DEL_READ)) 62 | self.del(ADD_READ | DEL_READ); 63 | 64 | if (self.has(ADD_WRITE | DEL_WRITE)) 65 | self.del(ADD_WRITE | DEL_WRITE); 66 | } 67 | 68 | pub fn equal(self: Set, events: u32) bool { 69 | if (self.has_any(DEL_READ | DEL_WRITE)) 70 | return false; 71 | 72 | var my_events: u32 = 0; 73 | 74 | if (self.has(ADD_READ)) 75 | my_events |= EVENTS.read; 76 | 77 | if (self.has(ADD_WRITE)) 78 | my_events |= EVENTS.write; 79 | 80 | return my_events == events; 81 | } 82 | }; 83 | }; 84 | 85 | // ============================================================= 86 | 87 | comptime { 88 | // @compileLog("sizeof(Fd):", @sizeOf(Fd)); 89 | // @compileLog("sizeof(anyframe):", @sizeOf(anyframe)); 90 | // @compileLog("sizeof(?anyframe):", @sizeOf(?anyframe)); 91 | } 92 | 93 | /// wrap the raw fd to work with evloop 94 | pub const Fd = struct { 95 | read_frame: ?anyframe = null, // waiting for readable event 96 | write_frame: ?anyframe = null, // waiting for writable event 97 | fd: c_int, 98 | rc: Rc = .{}, 99 | 100 | /// ownership of `fd` is transferred to `fdobj` 101 | pub fn new(fd: c_int) *Fd { 102 | const self = g.allocator.create(Fd) catch unreachable; 103 | self.* = .{ .fd = fd }; 104 | return self; 105 | } 106 | 107 | pub fn ref(self: *Fd) *Fd { 108 | self.rc.ref(); 109 | return self; 110 | } 111 | 112 | pub fn unref(self: *Fd) void { 113 | return self.free(); 114 | } 115 | 116 | pub fn free(self: *Fd) void { 117 | if (self.rc.unref() > 0) return; 118 | 119 | assert(self.read_frame == null); 120 | assert(self.write_frame == null); 121 | 122 | g.evloop.on_close_fd(self); 123 | _ = cc.close(self.fd); 124 | 125 | g.allocator.destroy(self); 126 | 127 | // record to the destroyed list, see `evloop.run()` 128 | g.evloop.destroyed.put(g.allocator, self, {}) catch unreachable; 129 | } 130 | 131 | pub fn interest_events(self: *const Fd) u32 { 132 | var events: u32 = 0; 133 | 134 | if (self.read_frame != null) 135 | events |= EVENTS.read; 136 | 137 | if (self.write_frame != null) 138 | events |= EVENTS.write; 139 | 140 | return events; 141 | } 142 | }; 143 | 144 | // ============================================================= 145 | 146 | /// epoll_event is a packed struct and need to be wrapped 147 | const Ev = opaque { 148 | /// raw type 149 | pub const Raw = c.struct_epoll_event; 150 | 151 | pub const SIZE = @sizeOf(Raw); 152 | pub const ALIGN = @alignOf(Raw); 153 | 154 | /// value type 155 | pub const V = Array(1); 156 | 157 | /// array type 158 | pub fn Array(comptime N: comptime_int) type { 159 | // it is currently not possible to directly return an array type with the align attribute 160 | // https://github.com/ziglang/zig/issues/7465 161 | return struct { 162 | buf: [N * SIZE]u8 align(ALIGN), 163 | 164 | // ======================= for Array ======================= 165 | 166 | const Self = @This(); 167 | 168 | pub inline fn at(self: *Self, i: usize) *Ev { 169 | return from(&self.buf[i * SIZE]); 170 | } 171 | 172 | /// return N 173 | pub inline fn len(_: *const Self) usize { 174 | return N; 175 | } 176 | 177 | // ======================= for Value ======================= 178 | 179 | pub inline fn init(events: u32, fdobj: *const Fd) V { 180 | var v: V = undefined; 181 | v.ptr().set_events(events); 182 | v.ptr().set_fdobj(fdobj); 183 | return v; 184 | } 185 | 186 | pub inline fn ptr(v: *V) *Ev { 187 | return v.at(0); 188 | } 189 | }; 190 | } 191 | 192 | pub inline fn from(ptr: anytype) Ptr(Ev, @TypeOf(ptr)) { 193 | return @ptrCast(Ptr(Ev, @TypeOf(ptr)), ptr); 194 | } 195 | 196 | pub inline fn get_events(self: *const Ev) u32 { 197 | return c.epev_get_events(self); 198 | } 199 | 200 | pub inline fn get_fdobj(self: *const Ev) *Fd { 201 | return cc.ptrcast(*Fd, c.epev_get_ptrdata(self)); 202 | } 203 | 204 | pub inline fn set_events(self: *Ev, events: u32) void { 205 | return c.epev_set_events(self, events); 206 | } 207 | 208 | pub inline fn set_fdobj(self: *Ev, fdobj: *const Fd) void { 209 | return c.epev_set_ptrdata(self, fdobj); 210 | } 211 | }; 212 | 213 | // ============================================================= 214 | 215 | pub noinline fn init() EvLoop { 216 | const epfd = cc.epoll_create1(c.EPOLL_CLOEXEC) orelse { 217 | log.err(@src(), "epoll_create() failed: (%d) %m", .{cc.errno()}); 218 | cc.exit(1); 219 | }; 220 | return .{ .epfd = epfd }; 221 | } 222 | 223 | /// return true if ok (internal api) 224 | noinline fn ctl(self: *EvLoop, op: c_int, fd: c_int, ev: ?*Ev) bool { 225 | cc.epoll_ctl(self.epfd, op, fd, ev) orelse { 226 | const op_name = switch (op) { 227 | c.EPOLL_CTL_ADD => "ADD", 228 | c.EPOLL_CTL_MOD => "MOD", 229 | c.EPOLL_CTL_DEL => "DEL", 230 | else => unreachable, 231 | }; 232 | const events = if (ev) |e| cc.to_ulong(e.get_events()) else 0; 233 | log.err(@src(), "epoll_ctl(%d, %s, %d, events:%lu) failed: (%d) %m", .{ self.epfd, op_name, fd, events, cc.errno() }); 234 | return false; 235 | }; 236 | return true; 237 | } 238 | 239 | /// return true if ok 240 | fn add(self: *EvLoop, fdobj: *const Fd, events: u32) bool { 241 | var ev = Ev.V.init(events, fdobj); 242 | return self.ctl(c.EPOLL_CTL_ADD, fdobj.fd, ev.ptr()); 243 | } 244 | 245 | /// return true if ok 246 | fn mod(self: *EvLoop, fdobj: *const Fd, events: u32) bool { 247 | var ev = Ev.V.init(events, fdobj); 248 | return self.ctl(c.EPOLL_CTL_MOD, fdobj.fd, ev.ptr()); 249 | } 250 | 251 | /// return true if ok 252 | fn del(self: *EvLoop, fdobj: *const Fd) bool { 253 | return self.ctl(c.EPOLL_CTL_DEL, fdobj.fd, null); 254 | } 255 | 256 | // ====================================================================== 257 | 258 | fn set_frame(fdobj: *Fd, comptime field_name: []const u8, frame: anyframe) void { 259 | assert(@field(fdobj, field_name) == null); 260 | @field(fdobj, field_name) = frame; 261 | } 262 | 263 | /// `frame` used for assert() check 264 | fn unset_frame(fdobj: *Fd, comptime field_name: []const u8, frame: anyframe) void { 265 | assert(@field(fdobj, field_name) == frame); 266 | @field(fdobj, field_name) = null; 267 | } 268 | 269 | /// before suspend {} 270 | fn add_readable(self: *EvLoop, fdobj: *Fd, frame: anyframe) void { 271 | set_frame(fdobj, "read_frame", frame); 272 | return self.cache_change(fdobj, Change.ADD_READ); 273 | } 274 | 275 | /// after suspend {} 276 | fn del_readable(self: *EvLoop, fdobj: *Fd, frame: anyframe) void { 277 | unset_frame(fdobj, "read_frame", frame); 278 | return self.cache_change(fdobj, Change.DEL_READ); 279 | } 280 | 281 | /// before suspend {} 282 | fn add_writable(self: *EvLoop, fdobj: *Fd, frame: anyframe) void { 283 | set_frame(fdobj, "write_frame", frame); 284 | return self.cache_change(fdobj, Change.ADD_WRITE); 285 | } 286 | 287 | /// after suspend {} 288 | fn del_writable(self: *EvLoop, fdobj: *Fd, frame: anyframe) void { 289 | unset_frame(fdobj, "write_frame", frame); 290 | return self.cache_change(fdobj, Change.DEL_WRITE); 291 | } 292 | 293 | fn cache_change(self: *EvLoop, fdobj: *const Fd, change: Change.T) void { 294 | const v = self.change_list.getOrPut(g.allocator, fdobj) catch unreachable; 295 | const change_set = v.value_ptr; 296 | if (v.found_existing) { 297 | change_set.add(change); 298 | if (change_set.is_empty()) 299 | assert(self.change_list.remove(fdobj)); 300 | } else { 301 | change_set.* = .{}; 302 | change_set.add(change); 303 | assert(!change_set.is_empty()); 304 | } 305 | } 306 | 307 | fn apply_change(self: *EvLoop) void { 308 | var it = self.change_list.iterator(); 309 | while (it.next()) |v| { 310 | const fdobj = v.key_ptr.*; 311 | const change_set = v.value_ptr.*; 312 | 313 | const events = fdobj.interest_events(); 314 | assert(!change_set.is_empty()); 315 | 316 | if (events == 0) { 317 | // del 318 | assert(change_set.has_any(Change.DEL_READ | Change.DEL_WRITE)); 319 | assert(self.del(fdobj)); 320 | } else { 321 | // add or mod 322 | if (change_set.equal(events)) 323 | assert(self.add(fdobj, events | c.EPOLLET)) 324 | else 325 | assert(self.mod(fdobj, events | c.EPOLLET)); 326 | } 327 | } 328 | self.change_list.clearRetainingCapacity(); 329 | } 330 | 331 | fn on_close_fd(self: *EvLoop, fdobj: *const Fd) void { 332 | if (self.change_list.fetchRemove(fdobj)) |v| { 333 | if (v.value.has_any(Change.DEL_READ | Change.DEL_WRITE)) 334 | assert(self.del(fdobj)); 335 | } 336 | } 337 | 338 | // ======================================================================== 339 | 340 | const EVENTS = opaque { 341 | pub const read: u32 = c.EPOLLIN | c.EPOLLRDHUP | c.EPOLLPRI; 342 | pub const write: u32 = c.EPOLLOUT; 343 | pub const err: u32 = c.EPOLLERR | c.EPOLLHUP; 344 | }; 345 | 346 | /// check for timeout events and handles them, then return the next timeout interval (ms) 347 | fn check_timeout(self: *EvLoop) c_int { 348 | _ = self; 349 | // TODO: implement a general-purpose timer manager. 350 | // there is currently only one timer, so do this. 351 | return root.check_timeout(); 352 | } 353 | 354 | pub fn run(self: *EvLoop) void { 355 | var evs: Ev.Array(64) = undefined; 356 | 357 | while (true) { 358 | // handling timeout events and get the next interval 359 | const timeout = nosuspend self.check_timeout(); 360 | 361 | // empty the list before starting a new epoll_wait 362 | self.destroyed.clearRetainingCapacity(); 363 | 364 | // apply the event changes (epoll_ctl) 365 | self.apply_change(); 366 | 367 | // waiting for I/O events 368 | const n = cc.epoll_wait(self.epfd, evs.at(0), cc.to_int(evs.len()), timeout) orelse b: { 369 | if (cc.errno() == c.EINTR) break :b -1; 370 | log.err(@src(), "epoll_wait(%d) failed: (%d) %m", .{ self.epfd, cc.errno() }); 371 | cc.exit(1); 372 | }; 373 | 374 | // handling I/O events 375 | var i: c_int = 0; 376 | while (i < n) : (i += 1) { 377 | const ev = evs.at(cc.to_usize(i)); 378 | 379 | const revents = ev.get_events(); 380 | const fdobj = ev.get_fdobj(); 381 | 382 | // check if `fdobj` has been destroyed 383 | if (self.destroyed.contains(fdobj)) 384 | continue; 385 | 386 | // add ref count 387 | _ = fdobj.ref(); 388 | defer fdobj.unref(); 389 | 390 | if (fdobj.read_frame != null and revents & (EVENTS.read | EVENTS.err) != 0) 391 | co.do_resume(fdobj.read_frame.?); 392 | 393 | if (fdobj.write_frame != null and revents & (EVENTS.write | EVENTS.err) != 0) 394 | co.do_resume(fdobj.write_frame.?); 395 | } 396 | } 397 | } 398 | 399 | // ======================================================================== 400 | 401 | // socket API (non-blocking + async) 402 | 403 | comptime { 404 | assert(c.EAGAIN == c.EWOULDBLOCK); 405 | } 406 | 407 | /// used for external modules, not for this module: 408 | /// because the async-call chains consume at least 24 bytes per level (x86_64) 409 | pub fn wait_readable(self: *EvLoop, fdobj: *Fd) void { 410 | self.add_readable(fdobj, @frame()); 411 | suspend {} 412 | self.del_readable(fdobj, @frame()); 413 | } 414 | 415 | /// used for external modules, not for this module: 416 | /// because the async-call chains consume at least 24 bytes per level (x86_64) 417 | pub fn wait_writable(self: *EvLoop, fdobj: *Fd) void { 418 | self.add_writable(fdobj, @frame()); 419 | suspend {} 420 | self.del_writable(fdobj, @frame()); 421 | } 422 | 423 | pub fn connect(self: *EvLoop, fdobj: *Fd, addr: *const cc.SockAddr) ?void { 424 | cc.connect(fdobj.fd, addr) orelse { 425 | if (cc.errno() != c.EINPROGRESS) 426 | return null; 427 | 428 | self.add_writable(fdobj, @frame()); 429 | suspend {} 430 | self.del_writable(fdobj, @frame()); 431 | 432 | if (net.getsockopt_int(fdobj.fd, c.SOL_SOCKET, c.SO_ERROR, "SO_ERROR")) |err| { 433 | if (err == 0) return; 434 | cc.set_errno(err); 435 | return null; 436 | } else { 437 | // getsockopt failed 438 | return null; 439 | } 440 | }; 441 | } 442 | 443 | pub fn accept(self: *EvLoop, fdobj: *Fd, src_addr: ?*cc.SockAddr) ?c_int { 444 | while (true) { 445 | return cc.accept4(fdobj.fd, src_addr, c.SOCK_NONBLOCK | c.SOCK_CLOEXEC) orelse { 446 | if (cc.errno() != c.EAGAIN) 447 | return null; 448 | 449 | self.add_readable(fdobj, @frame()); 450 | suspend {} 451 | self.del_readable(fdobj, @frame()); 452 | 453 | continue; 454 | }; 455 | } 456 | } 457 | 458 | pub fn read(self: *EvLoop, fdobj: *Fd, buf: []u8) ?usize { 459 | while (true) { 460 | return cc.read(fdobj.fd, buf) orelse { 461 | if (cc.errno() != c.EAGAIN) 462 | return null; 463 | 464 | self.add_readable(fdobj, @frame()); 465 | suspend {} 466 | self.del_readable(fdobj, @frame()); 467 | 468 | continue; 469 | }; 470 | } 471 | } 472 | 473 | pub fn recvfrom(self: *EvLoop, fdobj: *Fd, buf: []u8, flags: c_int, src_addr: *cc.SockAddr) ?usize { 474 | while (true) { 475 | return cc.recvfrom(fdobj.fd, buf, flags, src_addr) orelse { 476 | if (cc.errno() != c.EAGAIN) 477 | return null; 478 | 479 | self.add_readable(fdobj, @frame()); 480 | suspend {} 481 | self.del_readable(fdobj, @frame()); 482 | 483 | continue; 484 | }; 485 | } 486 | } 487 | 488 | pub fn recv(self: *EvLoop, fdobj: *Fd, buf: []u8, flags: c_int) ?usize { 489 | while (true) { 490 | return cc.recv(fdobj.fd, buf, flags) orelse { 491 | if (cc.errno() != c.EAGAIN) 492 | return null; 493 | 494 | self.add_readable(fdobj, @frame()); 495 | suspend {} 496 | self.del_readable(fdobj, @frame()); 497 | 498 | continue; 499 | }; 500 | } 501 | } 502 | 503 | const ReadErr = error{ eof, other }; 504 | 505 | pub fn recv_exactly(self: *EvLoop, fdobj: *Fd, buf: []u8, flags: c_int) ReadErr!void { 506 | var nread: usize = 0; 507 | while (nread < buf.len) { 508 | const n = self.recv(fdobj, buf[nread..], flags) orelse 509 | return ReadErr.other; 510 | if (n == 0) 511 | return ReadErr.eof; 512 | nread += n; 513 | // https://man7.org/linux/man-pages/man7/epoll.7.html 514 | if (nread < buf.len) { 515 | self.add_readable(fdobj, @frame()); 516 | suspend {} 517 | self.del_readable(fdobj, @frame()); 518 | } 519 | } 520 | } 521 | 522 | pub fn send(self: *EvLoop, fdobj: *Fd, data: []const u8, flags: c_int) ?void { 523 | var nsend: usize = 0; 524 | while (nsend < data.len) { 525 | const n = cc.send(fdobj.fd, data[nsend..], flags) orelse b: { 526 | if (cc.errno() != c.EAGAIN) 527 | return null; 528 | break :b 0; 529 | }; 530 | nsend += n; 531 | // https://man7.org/linux/man-pages/man7/epoll.7.html 532 | if (nsend < data.len) { 533 | self.add_writable(fdobj, @frame()); 534 | suspend {} 535 | self.del_writable(fdobj, @frame()); 536 | } 537 | } 538 | } 539 | 540 | /// the `iov` struct will be modified 541 | pub fn sendmsg(self: *EvLoop, fdobj: *Fd, msg: *const cc.msghdr_t, flags: c_int) ?void { 542 | var remain_len: usize = msg.calc_len(); 543 | while (remain_len > 0) { 544 | const n = cc.sendmsg(fdobj.fd, msg, flags) orelse b: { 545 | if (cc.errno() != c.EAGAIN) 546 | return null; 547 | break :b 0; 548 | }; 549 | if (n > 0) { 550 | remain_len -= n; 551 | msg.skip_iov(n); 552 | } 553 | // https://man7.org/linux/man-pages/man7/epoll.7.html 554 | if (remain_len > 0) { 555 | self.add_writable(fdobj, @frame()); 556 | suspend {} 557 | self.del_writable(fdobj, @frame()); 558 | } 559 | } 560 | } 561 | -------------------------------------------------------------------------------- /src/ListNode.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const g = @import("g.zig"); 3 | const testing = std.testing; 4 | const assert = std.debug.assert; 5 | 6 | // ===================================================== 7 | 8 | const ListNode = @This(); 9 | 10 | prev: *ListNode, 11 | next: *ListNode, 12 | 13 | // =================== `list_head(sentinel)` =================== 14 | 15 | /// empty list (sentinel node) 16 | pub fn init(list: *ListNode) void { 17 | list.prev = list; 18 | list.next = list; 19 | } 20 | 21 | /// first node 22 | pub inline fn head(list: *const ListNode) *ListNode { 23 | return list.next; 24 | } 25 | 26 | /// last node 27 | pub inline fn tail(list: *const ListNode) *ListNode { 28 | return list.prev; 29 | } 30 | 31 | /// is sentinel node 32 | pub inline fn is_empty(list: *const ListNode) bool { 33 | return list.head() == list; 34 | } 35 | 36 | /// `unlink(node)` and/or `free(node)` is safe 37 | pub fn iterator(list: *const ListNode) Iterator { 38 | return .{ 39 | .sentinel = list, 40 | .node = list.head(), 41 | }; 42 | } 43 | 44 | /// `unlink(node)` and/or `free(node)` is safe 45 | pub fn reverse_iterator(list: *const ListNode) ReverseIterator { 46 | return .{ 47 | .sentinel = list, 48 | .node = list.tail(), 49 | }; 50 | } 51 | 52 | pub const Iterator = struct { 53 | sentinel: *const ListNode, 54 | node: *ListNode, 55 | 56 | pub fn next(it: *Iterator) ?*ListNode { 57 | const node = it.node; 58 | if (node != it.sentinel) { 59 | it.node = node.next; 60 | return node; 61 | } 62 | return null; 63 | } 64 | }; 65 | 66 | pub const ReverseIterator = struct { 67 | sentinel: *const ListNode, 68 | node: *ListNode, 69 | 70 | pub fn next(it: *ReverseIterator) ?*ListNode { 71 | const node = it.node; 72 | if (node != it.sentinel) { 73 | it.node = node.prev; 74 | return node; 75 | } 76 | return null; 77 | } 78 | }; 79 | 80 | // =================== `node` =================== 81 | 82 | pub fn link_to_head(list: *ListNode, node: *ListNode) void { 83 | return node.link(list, list.head()); 84 | } 85 | 86 | pub fn link_to_tail(list: *ListNode, node: *ListNode) void { 87 | return node.link(list.tail(), list); 88 | } 89 | 90 | /// assume that the `node` is linked to the `list` 91 | pub fn move_to_head(list: *ListNode, node: *ListNode) void { 92 | if (node != list.head()) { 93 | node.unlink(); 94 | list.link_to_head(node); 95 | } 96 | } 97 | 98 | /// assume that the `node` is linked to the `list` 99 | pub fn move_to_tail(list: *ListNode, node: *ListNode) void { 100 | if (node != list.tail()) { 101 | node.unlink(); 102 | list.link_to_tail(node); 103 | } 104 | } 105 | 106 | fn link(node: *ListNode, prev: *ListNode, next: *ListNode) void { 107 | prev.next = node; 108 | node.prev = prev; 109 | node.next = next; 110 | next.prev = node; 111 | } 112 | 113 | /// `node.prev` and `node.next` are unmodified, use `node.init()` if needed. 114 | /// `list_head.unlink()` is not allowed unless `list_head` is an empty list. 115 | pub fn unlink(node: *const ListNode) void { 116 | node.prev.next = node.next; 117 | node.next.prev = node.prev; 118 | } 119 | 120 | // ========================================================= 121 | 122 | const Object = struct { 123 | id: u32, 124 | node: ListNode, 125 | 126 | pub fn from_node(node: *ListNode) *Object { 127 | return @fieldParentPtr(Object, "node", node); 128 | } 129 | }; 130 | 131 | pub fn @"test: linked list"() !void { 132 | var list: ListNode = undefined; 133 | list.init(); 134 | 135 | defer { 136 | var it = list.iterator(); 137 | while (it.next()) |node| { 138 | node.unlink(); 139 | g.allocator.destroy(Object.from_node(node)); 140 | // break; 141 | } 142 | 143 | assert(list.is_empty()); 144 | 145 | list.unlink(); 146 | list.unlink(); 147 | assert(list.is_empty()); 148 | } 149 | 150 | { 151 | var i: u32 = 1; 152 | while (i <= 5) : (i += 1) { 153 | const obj = try g.allocator.create(Object); 154 | obj.id = i; 155 | list.link_to_tail(&obj.node); 156 | } 157 | } 158 | 159 | { 160 | var it = list.iterator(); 161 | var id: u32 = 1; 162 | while (it.next()) |node| : (id += 1) { 163 | const obj = Object.from_node(node); 164 | try testing.expectEqual(id, obj.id); 165 | } 166 | } 167 | 168 | { 169 | var it = list.reverse_iterator(); 170 | var id: u32 = 5; 171 | while (it.next()) |node| : (id -= 1) { 172 | const obj = Object.from_node(node); 173 | try testing.expectEqual(id, obj.id); 174 | } 175 | } 176 | 177 | { 178 | // [1,2,3,4,5] => [1,3,4] 179 | var it = list.iterator(); 180 | while (it.next()) |node| { 181 | const obj = Object.from_node(node); 182 | if (obj.id == 2 or obj.id == 5) { 183 | node.unlink(); 184 | g.allocator.destroy(obj); 185 | } 186 | } 187 | 188 | var i: u32 = 0; 189 | const ids = [_]u32{ 1, 3, 4 }; 190 | var it2 = list.iterator(); 191 | while (it2.next()) |node| : (i += 1) { 192 | const obj = Object.from_node(node); 193 | try testing.expectEqual(ids[i], obj.id); 194 | } 195 | } 196 | 197 | // link_to_head 198 | var l: ListNode = undefined; 199 | l.init(); 200 | 201 | defer { 202 | var it = l.iterator(); 203 | while (it.next()) |node| { 204 | node.unlink(); 205 | const obj = Object.from_node(node); 206 | g.allocator.destroy(obj); 207 | } 208 | 209 | assert(l.is_empty()); 210 | 211 | l.unlink(); 212 | assert(l.is_empty()); 213 | } 214 | 215 | { 216 | var i: u32 = 3; 217 | while (i > 0) : (i -= 1) { 218 | const obj = try g.allocator.create(Object); 219 | obj.id = i; 220 | l.link_to_head(&obj.node); 221 | } 222 | } 223 | 224 | { 225 | var id: u32 = 1; 226 | var it = l.iterator(); 227 | while (it.next()) |node| : (id += 1) { 228 | const obj = Object.from_node(node); 229 | try testing.expectEqual(id, obj.id); 230 | } 231 | } 232 | 233 | { 234 | var id: u32 = 3; 235 | var it = l.reverse_iterator(); 236 | while (it.next()) |node| : (id -= 1) { 237 | const obj = Object.from_node(node); 238 | try testing.expectEqual(id, obj.id); 239 | } 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /src/Rc.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const assert = std.debug.assert; 3 | 4 | // ========================================== 5 | 6 | const Rc = @This(); 7 | 8 | ref_count: u32 = 1, 9 | 10 | // ========================================== 11 | 12 | pub inline fn ref(self: *Rc) void { 13 | assert(self.ref_count > 0); 14 | self.ref_count += 1; 15 | } 16 | 17 | /// return the updated ref_count 18 | pub inline fn unref(self: *Rc) u32 { 19 | assert(self.ref_count > 0); 20 | self.ref_count -= 1; 21 | return self.ref_count; 22 | } 23 | -------------------------------------------------------------------------------- /src/StrList.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const g = @import("g.zig"); 3 | const cc = @import("cc.zig"); 4 | const SentinelVector = @import("sentinel_vector.zig").SentinelVector; 5 | 6 | // ================================================== 7 | 8 | const StrList = @This(); 9 | 10 | vec: SentinelVector(?cc.ConstStr, null) = .{}, 11 | 12 | // ================================================== 13 | 14 | /// a copy of string `str` will be created (strdup) 15 | /// if the string already exists, it will not be added 16 | pub fn add(self: *StrList, str: []const u8) void { 17 | for (self.items()) |cstr| { 18 | if (std.mem.eql(u8, cc.strslice_c(cstr), str)) 19 | return; 20 | } 21 | self.vec.append().* = (g.allocator.dupeZ(u8, str) catch unreachable).ptr; 22 | } 23 | 24 | pub fn items_z(self: *const StrList) [:null]?cc.ConstStr { 25 | return self.vec.items; 26 | } 27 | 28 | pub fn items(self: *const StrList) []cc.ConstStr { 29 | return @ptrCast([]cc.ConstStr, self.items_z()); 30 | } 31 | 32 | pub fn is_null(self: *const StrList) bool { 33 | return self.vec.is_null(); 34 | } 35 | 36 | pub fn is_empty(self: *const StrList) bool { 37 | return self.vec.is_empty(); 38 | } 39 | -------------------------------------------------------------------------------- /src/c.zig: -------------------------------------------------------------------------------- 1 | // usually you should only have one `@cImport` in your entire application, 2 | // because it saves the compiler from invoking clang multiple times, 3 | // and prevents inline functions from being duplicated. 4 | 5 | // import into the current namespace (c.zig) 6 | // mainly used to access C constants, C typedefs 7 | // please give priority to using functions in the `cc` namespace 8 | pub usingnamespace @cImport({ 9 | @cDefine("_GNU_SOURCE", {}); 10 | 11 | @cInclude("stdio.h"); 12 | @cInclude("stdlib.h"); 13 | @cInclude("stdint.h"); 14 | @cInclude("stddef.h"); 15 | @cInclude("string.h"); 16 | @cInclude("errno.h"); 17 | @cInclude("unistd.h"); 18 | @cInclude("signal.h"); 19 | @cInclude("time.h"); 20 | @cInclude("fcntl.h"); 21 | @cInclude("sys/types.h"); 22 | @cInclude("sys/epoll.h"); 23 | @cInclude("sys/socket.h"); 24 | @cInclude("sys/mman.h"); 25 | @cInclude("arpa/inet.h"); 26 | @cInclude("netinet/in.h"); 27 | @cInclude("netinet/tcp.h"); 28 | @cInclude("linux/limits.h"); 29 | 30 | @cInclude("src/net.h"); 31 | @cInclude("src/misc.h"); 32 | @cInclude("src/wolfssl.h"); 33 | }); 34 | 35 | /// assuming CHAR_BIT=8 36 | /// character type (signed or unsigned) 37 | /// currently zig assumes it is **unsigned** 38 | /// https://github.com/ziglang/zig/issues/875 39 | pub const char = u8; 40 | 41 | /// assuming CHAR_BIT=8 42 | /// signed integer type 43 | pub const schar = i8; 44 | 45 | /// assuming CHAR_BIT=8 46 | /// unsigned integer type 47 | pub const uchar = u8; 48 | 49 | /// assuming IEEE-754 binary32 format 50 | /// single precision floating-point type 51 | pub const float = f32; 52 | 53 | /// assuming IEEE-754 binary64 format 54 | /// double precision floating-point type 55 | pub const double = f64; 56 | 57 | /// u16 big-endian (used to label the byte order) 58 | pub const be16 = u16; 59 | 60 | /// u32 big-endian (used to label the byte order) 61 | pub const be32 = u32; 62 | 63 | /// u64 big-endian (used to label the byte order) 64 | pub const be64 = u64; 65 | -------------------------------------------------------------------------------- /src/cc.zig: -------------------------------------------------------------------------------- 1 | //! - provide type-safety version of C functions 2 | //! - fix improperly translated C code/declarations 3 | 4 | const std = @import("std"); 5 | const c = @import("c.zig"); 6 | const g = @import("g.zig"); 7 | const log = @import("log.zig"); 8 | const fmtchk = @import("fmtchk.zig"); 9 | const meta = std.meta; 10 | const testing = std.testing; 11 | const assert = std.debug.assert; 12 | const isConstPtr = meta.trait.isConstPtr; 13 | const isManyItemPtr = meta.trait.isManyItemPtr; 14 | const isSlice = meta.trait.isSlice; 15 | 16 | // ============================================================== 17 | 18 | /// in stage1, `if (b) expr1 else expr2` expressions do not compile correctly \ 19 | /// if they are present in the argument tuple, so use this function to wrap it 20 | pub inline fn b2v(b: bool, true_v: anytype, false_v: anytype) @TypeOf(true_v, false_v) { 21 | return if (b) true_v else false_v; 22 | } 23 | 24 | /// in stage1, `if (b) expr1 else expr2` expressions do not compile correctly \ 25 | /// if they are present in the argument tuple, so use this function to wrap it 26 | pub inline fn b2s(b: bool, true_v: ConstStr, false_v: ConstStr) ConstStr { 27 | return if (b) true_v else false_v; 28 | } 29 | 30 | // ============================================================== 31 | 32 | pub const Str = [*:0]u8; 33 | pub const ConstStr = [*:0]const u8; 34 | 35 | // ============================================================== 36 | 37 | pub fn comptime_tostr(comptime value: anytype) [:0]const u8 { 38 | return std.fmt.comptimePrint("{}", .{value}); 39 | } 40 | 41 | /// remove const qualification of pointer `ptr` 42 | /// TODO: zig 0.11 has @constCast() 43 | pub inline fn remove_const(ptr: anytype) RemoveConst(@TypeOf(ptr)) { 44 | const P = @TypeOf(ptr); 45 | if (comptime isSlice(P)) { 46 | return if (comptime meta.sentinel(P)) |sentinel| 47 | remove_const(ptr.ptr)[0..ptr.len :sentinel] 48 | else 49 | remove_const(ptr.ptr)[0..ptr.len]; 50 | } 51 | return @intToPtr(RemoveConst(P), @ptrToInt(ptr)); 52 | } 53 | 54 | /// remove const qualification of pointer type `T` 55 | pub fn RemoveConst(comptime T: type) type { 56 | if (isConstPtr(T)) { 57 | var info = @typeInfo(T); 58 | info.Pointer.is_const = false; 59 | return @Type(info); 60 | } 61 | return T; 62 | } 63 | 64 | /// return the `bytes` type of the given `pointer` type, preserving the `const` attribute 65 | pub fn Bytes(comptime P: type, t: enum { ptr, slice }) type { 66 | if (isConstPtr(P)) 67 | return if (t == .ptr) [*]const u8 else []const u8 68 | else 69 | return if (t == .ptr) [*]u8 else []u8; 70 | } 71 | 72 | /// return the `*const T` or `*T` (depends on the `P`) 73 | pub fn Ptr(comptime T: type, comptime P: type) type { 74 | return if (isConstPtr(P)) *const T else *T; 75 | } 76 | 77 | // ============================================================== 78 | 79 | /// return `p1 - p2` (same semantics as C) 80 | /// https://github.com/ziglang/zig/issues/1738 81 | pub inline fn ptrdiff(comptime T: type, p1: [*]const T, p2: [*]const T) isize { 82 | const addr1 = to_isize(@ptrToInt(p1)); 83 | const addr2 = to_isize(@ptrToInt(p2)); 84 | return @divExact(addr1 - addr2, @sizeOf(T)); 85 | } 86 | 87 | /// return `p1 - p2`, assume the result is non-negative 88 | pub inline fn ptrdiff_u(comptime T: type, p1: [*]const T, p2: [*]const T) usize { 89 | return to_usize(ptrdiff(T, p1, p2)); 90 | } 91 | 92 | /// `@ptrCast(P, @alignCast(alignment, ptr))` 93 | pub inline fn ptrcast(comptime P: type, ptr: anytype) P { 94 | return @ptrCast(P, @alignCast(@alignOf(meta.Child(P)), ptr)); 95 | } 96 | 97 | fn IntCast(comptime DestType: type) type { 98 | return struct { 99 | pub inline fn cast(integer: anytype) DestType { 100 | return @intCast(DestType, integer); 101 | } 102 | }; 103 | } 104 | 105 | pub const to_schar = IntCast(c.schar).cast; 106 | pub const to_uchar = IntCast(c.uchar).cast; 107 | 108 | pub const to_short = IntCast(c_short).cast; 109 | pub const to_ushort = IntCast(c_ushort).cast; 110 | 111 | pub const to_int = IntCast(c_int).cast; 112 | pub const to_uint = IntCast(c_uint).cast; 113 | 114 | pub const to_long = IntCast(c_long).cast; 115 | pub const to_ulong = IntCast(c_ulong).cast; 116 | 117 | pub const to_longlong = IntCast(c_longlong).cast; 118 | pub const to_ulonglong = IntCast(c_ulonglong).cast; 119 | 120 | pub const to_isize = IntCast(isize).cast; 121 | pub const to_usize = IntCast(usize).cast; 122 | 123 | pub const to_i8 = IntCast(i8).cast; 124 | pub const to_u8 = IntCast(u8).cast; 125 | 126 | pub const to_i16 = IntCast(i16).cast; 127 | pub const to_u16 = IntCast(u16).cast; 128 | 129 | pub const to_i32 = IntCast(i32).cast; 130 | pub const to_u32 = IntCast(u32).cast; 131 | 132 | pub const to_i64 = IntCast(i64).cast; 133 | pub const to_u64 = IntCast(u64).cast; 134 | 135 | // ============================================================== 136 | 137 | pub inline fn calc_hashv(mem: []const u8) c_uint { 138 | return c.calc_hashv(mem.ptr, mem.len); 139 | } 140 | 141 | pub inline fn memeql(a: []const u8, b: []const u8) bool { 142 | return a.len == b.len and c.memcmp(a.ptr, b.ptr, a.len) == 0; 143 | } 144 | 145 | /// avoid static buffers all over the place, wasting memory 146 | pub noinline fn static_buf(size: usize) []u8 { 147 | const static = struct { 148 | var buf: []u8 = &.{}; 149 | }; 150 | if (size > static.buf.len) { 151 | if (static.buf.len == 0) { 152 | static.buf = g.allocator.alloc(u8, size) catch unreachable; 153 | } else if (g.allocator.resize(static.buf, size)) |buf| { 154 | static.buf = buf; 155 | } else { 156 | g.allocator.free(static.buf); 157 | static.buf = g.allocator.alloc(u8, size) catch unreachable; 158 | } 159 | } 160 | return static.buf.ptr[0..size]; 161 | } 162 | 163 | /// convert to C string (static buffer) 164 | pub inline fn to_cstr(str: []const u8) Str { 165 | return to_cstr_x(&.{str}); 166 | } 167 | 168 | /// convert to C string (static buffer) 169 | pub noinline fn to_cstr_x(str_list: []const []const u8) Str { 170 | var total_len: usize = 0; 171 | for (str_list) |str| 172 | total_len += str.len; 173 | 174 | const buf = static_buf(total_len + 1); 175 | 176 | var ptr = buf.ptr; 177 | for (str_list) |str| { 178 | @memcpy(ptr, str.ptr, str.len); 179 | ptr += str.len; 180 | } 181 | ptr[0] = 0; 182 | 183 | return @ptrCast(Str, buf.ptr); 184 | } 185 | 186 | /// end with sentinel 0 187 | pub inline fn is_cstr(comptime S: type) bool { 188 | return @typeInfo(StrSlice(S, false)).Pointer.sentinel != null; 189 | } 190 | 191 | /// string => []u8, []const u8, [:0]u8, [:0]const u8 192 | pub inline fn strslice(str: anytype) StrSlice(@TypeOf(str), false) { 193 | const S = @TypeOf(str); 194 | if (comptime isManyItemPtr(S)) { 195 | comptime assert(meta.sentinel(S).? == 0); 196 | return str[0..c.strlen(str) :0]; 197 | } 198 | return str; 199 | } 200 | 201 | /// string => []const u8, [:0]const u8 202 | pub inline fn strslice_c(str: anytype) StrSlice(@TypeOf(str), true) { 203 | return strslice(str); 204 | } 205 | 206 | fn StrSlice(comptime S: type, comptime force_const: bool) type { 207 | const info = @typeInfo(S); 208 | 209 | if (info != .Pointer) 210 | @compileError("expected pointer, found " ++ @typeName(S)); 211 | 212 | if (meta.Elem(S) != u8) 213 | @compileError("expected u8 pointer, found " ++ @typeName(S)); 214 | 215 | const sentinel = meta.sentinel(S); 216 | 217 | if (sentinel) |end| { 218 | if (end != 0) 219 | @compileError("expected sentinel 0, found " ++ @typeName(S)); 220 | } 221 | 222 | const ptr_info = info.Pointer; 223 | 224 | switch (ptr_info.size) { 225 | .One => if (@typeInfo(ptr_info.child) != .Array) 226 | @compileError("expected u8 array pointer, found " ++ @typeName(S)), 227 | 228 | .Many => if (sentinel == null) 229 | @compileError("expected many pointer with sentinel, found " ++ @typeName(S)), 230 | 231 | .Slice => {}, 232 | 233 | .C => @compileError("expected non-C pointer, found " ++ @typeName(S)), 234 | } 235 | 236 | if (force_const or ptr_info.is_const) { 237 | return if (sentinel != null) 238 | [:0]const u8 239 | else 240 | []const u8; 241 | } else { 242 | return if (sentinel != null) 243 | [:0]u8 244 | else 245 | []u8; 246 | } 247 | } 248 | 249 | // ============================================================== 250 | 251 | extern fn __errno_location() *c_int; 252 | 253 | pub inline fn errno() c_int { 254 | return __errno_location().*; 255 | } 256 | 257 | pub inline fn set_errno(err: c_int) void { 258 | __errno_location().* = err; 259 | } 260 | 261 | // ============================================================== 262 | 263 | pub const FILE = opaque {}; 264 | 265 | pub extern const stdin: *FILE; 266 | pub extern const stdout: *FILE; 267 | pub extern const stderr: *FILE; 268 | 269 | // ============================================================== 270 | 271 | pub inline fn fprintf(file: *FILE, comptime fmt: [:0]const u8, args: anytype) void { 272 | const raw = struct { 273 | extern fn fprintf(file: *FILE, fmt: ConstStr, ...) c_int; 274 | }; 275 | fmtchk.check(fmt, args); 276 | _ = @call(.{}, raw.fprintf, .{ file, fmt.ptr } ++ args); 277 | } 278 | 279 | /// print to stdout 280 | pub inline fn printf(comptime fmt: [:0]const u8, args: anytype) void { 281 | return fprintf(stdout, fmt, args); 282 | } 283 | 284 | /// print to stderr 285 | pub inline fn printf_err(comptime fmt: [:0]const u8, args: anytype) void { 286 | return fprintf(stderr, fmt, args); 287 | } 288 | 289 | /// print to string-buffer 290 | /// return the written c-string 291 | pub fn snprintf(buffer: []u8, comptime fmt: [:0]const u8, args: anytype) [:0]u8 { 292 | const raw = struct { 293 | extern fn snprintf(buf: [*]u8, len: usize, fmt: ConstStr, ...) c_int; 294 | }; 295 | 296 | fmtchk.check(fmt, args); 297 | 298 | // at least one character and the null terminator 299 | assert(buffer.len >= 2); 300 | 301 | // number of characters (not including the terminating null character) which would have been written to buffer if bufsz was ignored, 302 | // or a negative value if an encoding error (for string and character conversion specifiers) occurred 303 | const should_strlen = @call(.{}, raw.snprintf, .{ buffer.ptr, buffer.len, fmt.ptr } ++ args); 304 | 305 | // reserve space for '\0' 306 | if (0 <= should_strlen and should_strlen <= buffer.len - 1) 307 | return buffer[0..to_usize(should_strlen) :0]; 308 | 309 | // buffer space not enough 310 | if (should_strlen > 0) 311 | return buffer[0..(buffer.len - 1) :0]; 312 | 313 | // encoding error 314 | buffer[0] = 0; 315 | return buffer[0..0 :0]; 316 | } 317 | 318 | // ============================================================== 319 | 320 | pub inline fn setvbuf(file: *FILE, buffer: ?[*]u8, mode: c_int, size: usize) ?void { 321 | const raw = struct { 322 | extern fn setvbuf(file: *FILE, buffer: ?[*]u8, mode: c_int, size: usize) c_int; 323 | }; 324 | return if (raw.setvbuf(file, buffer, mode, size) != 0) null; 325 | } 326 | 327 | // ============================================================== 328 | 329 | pub inline fn time() c.time_t { 330 | const raw = struct { 331 | extern fn time(t: ?*c.time_t) c.time_t; 332 | }; 333 | return raw.time(null); 334 | } 335 | 336 | pub inline fn localtime(t: c.time_t) ?*c.struct_tm { 337 | const raw = struct { 338 | extern fn localtime(t: *const c.time_t) ?*c.struct_tm; 339 | }; 340 | return raw.localtime(&t); 341 | } 342 | 343 | // ============================================================== 344 | 345 | pub extern fn getenv(env_name: ConstStr) ?ConstStr; 346 | 347 | pub inline fn setenv(env_name: ConstStr, value: ConstStr, is_replace: bool) ?void { 348 | const raw = struct { 349 | extern fn setenv(env_name: ConstStr, value: ConstStr, is_replace: c_int) c_int; 350 | }; 351 | return if (raw.setenv(env_name, value, @boolToInt(is_replace)) == -1) null; 352 | } 353 | 354 | pub extern fn exit(status: c_int) noreturn; 355 | pub extern fn abort() noreturn; 356 | 357 | pub inline fn connect(fd: c_int, addr: *const SockAddr) ?void { 358 | const raw = struct { 359 | extern fn connect(fd: c_int, addr: *const anyopaque, addrlen: c.socklen_t) c_int; 360 | }; 361 | return if (raw.connect(fd, addr, addr.len()) == -1) null; 362 | } 363 | 364 | pub inline fn accept4(fd: c_int, addr: ?*SockAddr, flags: c_int) ?c_int { 365 | const raw = struct { 366 | extern fn accept4(fd: c_int, addr: ?*anyopaque, addrlen: ?*c.socklen_t, flags: c_int) c_int; 367 | }; 368 | var addrlen: c.socklen_t = @sizeOf(SockAddr); 369 | const p_addrlen = if (addr != null) &addrlen else null; 370 | const res = raw.accept4(fd, addr, p_addrlen, flags); 371 | return if (res >= 0) res else null; 372 | } 373 | 374 | pub inline fn send(fd: c_int, data: []const u8, flags: c_int) ?usize { 375 | const raw = struct { 376 | extern fn send(fd: c_int, buf: [*]const u8, len: usize, flags: c_int) isize; 377 | }; 378 | const n = raw.send(fd, data.ptr, data.len, flags); 379 | return if (n >= 0) to_usize(n) else null; 380 | } 381 | 382 | pub inline fn recv(fd: c_int, buf: []u8, flags: c_int) ?usize { 383 | const raw = struct { 384 | extern fn recv(fd: c_int, buf: [*]u8, len: usize, flags: c_int) isize; 385 | }; 386 | const n = raw.recv(fd, buf.ptr, buf.len, flags); 387 | return if (n >= 0) to_usize(n) else null; 388 | } 389 | 390 | pub inline fn sendto(fd: c_int, buf: []const u8, flags: c_int, addr: *const SockAddr) ?usize { 391 | const raw = struct { 392 | extern fn sendto(fd: c_int, buf: [*]const u8, len: usize, flags: c_int, addr: *const anyopaque, addrlen: c.socklen_t) isize; 393 | }; 394 | const n = raw.sendto(fd, buf.ptr, buf.len, flags, addr, addr.len()); 395 | return if (n >= 0) to_usize(n) else null; 396 | } 397 | 398 | pub inline fn recvfrom(fd: c_int, buf: []u8, flags: c_int, addr: *SockAddr) ?usize { 399 | const raw = struct { 400 | extern fn recvfrom(fd: c_int, buf: [*]u8, len: usize, flags: c_int, addr: *anyopaque, addrlen: *c.socklen_t) isize; 401 | }; 402 | var addrlen: c.socklen_t = @sizeOf(SockAddr); 403 | const n = raw.recvfrom(fd, buf.ptr, buf.len, flags, addr, &addrlen); 404 | return if (n >= 0) to_usize(n) else null; 405 | } 406 | 407 | pub inline fn read(fd: c_int, buf: []u8) ?usize { 408 | const raw = struct { 409 | extern fn read(fd: c_int, buf: [*]u8, len: usize) isize; 410 | }; 411 | const n = raw.read(fd, buf.ptr, buf.len); 412 | return if (n >= 0) to_usize(n) else null; 413 | } 414 | 415 | pub inline fn write(fd: c_int, buf: []const u8) ?usize { 416 | const raw = struct { 417 | extern fn write(fd: c_int, buf: [*]const u8, len: usize) isize; 418 | }; 419 | const n = raw.write(fd, buf.ptr, buf.len); 420 | return if (n >= 0) to_usize(n) else null; 421 | } 422 | 423 | pub inline fn pipe2(fds: *[2]c_int, flags: c_int) ?void { 424 | const raw = struct { 425 | extern fn pipe2(fds: *[2]c_int, flags: c_int) c_int; 426 | }; 427 | return if (raw.pipe2(fds, flags) == -1) null; 428 | } 429 | 430 | pub inline fn socket(family: c_int, type_: c_int, protocol: c_int) ?c_int { 431 | const raw = struct { 432 | extern fn socket(family: c_int, type: c_int, protocol: c_int) c_int; 433 | }; 434 | const res = raw.socket(family, type_, protocol); 435 | return if (res >= 0) res else null; 436 | } 437 | 438 | pub inline fn open(filename: ConstStr, flags: c_int, newfile_mode: ?c.mode_t) ?c_int { 439 | const raw = struct { 440 | extern fn open(file: ConstStr, oflag: c_int, ...) c_int; 441 | }; 442 | const fd = raw.open(filename, flags, newfile_mode orelse 0); 443 | return if (fd >= 0) fd else null; 444 | } 445 | 446 | pub inline fn is_dir(path: ConstStr) bool { 447 | return c.is_dir(path); 448 | } 449 | 450 | pub inline fn fstat_size(fd: c_int) ?usize { 451 | const sz = c.fstat_size(fd); 452 | return if (sz >= 0) to_usize(sz) else null; 453 | } 454 | 455 | pub inline fn close(fd: c_int) ?void { 456 | const raw = struct { 457 | extern fn close(fd: c_int) c_int; 458 | }; 459 | return if (raw.close(fd) == -1) null; 460 | } 461 | 462 | pub inline fn bind(fd: c_int, addr: *const SockAddr) ?void { 463 | const raw = struct { 464 | extern fn bind(fd: c_int, addr: *const anyopaque, addrlen: c.socklen_t) c_int; 465 | }; 466 | return if (raw.bind(fd, addr, addr.len()) == -1) null; 467 | } 468 | 469 | pub inline fn listen(fd: c_int, backlog: c_int) ?void { 470 | const raw = struct { 471 | extern fn listen(fd: c_int, backlog: c_int) c_int; 472 | }; 473 | return if (raw.listen(fd, backlog) == -1) null; 474 | } 475 | 476 | pub inline fn getsockopt(fd: c_int, level: c_int, opt: c_int, optval: *anyopaque, optlen: *c.socklen_t) ?void { 477 | const raw = struct { 478 | extern fn getsockopt(fd: c_int, level: c_int, opt: c_int, optval: *anyopaque, optlen: *c.socklen_t) c_int; 479 | }; 480 | return if (raw.getsockopt(fd, level, opt, optval, optlen) == -1) null; 481 | } 482 | 483 | pub inline fn setsockopt(fd: c_int, level: c_int, opt: c_int, optval: *const anyopaque, optlen: c.socklen_t) ?void { 484 | const raw = struct { 485 | extern fn setsockopt(fd: c_int, level: c_int, opt: c_int, optval: *const anyopaque, optlen: c.socklen_t) c_int; 486 | }; 487 | return if (raw.setsockopt(fd, level, opt, optval, optlen) == -1) null; 488 | } 489 | 490 | pub inline fn getsockopt_int(fd: c_int, level: c_int, opt: c_int) ?c_int { 491 | var res: c_int = undefined; 492 | var reslen: c.socklen_t = @sizeOf(c_int); 493 | getsockopt(fd, level, opt, &res, &reslen) orelse return null; 494 | assert(reslen == @sizeOf(c_int)); 495 | return res; 496 | } 497 | 498 | pub inline fn setsockopt_int(fd: c_int, level: c_int, opt: c_int, optval: c_int) ?void { 499 | return setsockopt(fd, level, opt, &optval, @sizeOf(c_int)); 500 | } 501 | 502 | pub extern fn ntohs(net_v: u16) u16; 503 | pub extern fn ntohl(net_v: u32) u32; 504 | 505 | pub extern fn htons(host_v: u16) u16; 506 | pub extern fn htonl(host_v: u32) u32; 507 | 508 | pub inline fn inet_pton(family: c_int, str_ip: ConstStr, net_ip: *anyopaque) bool { 509 | const raw = struct { 510 | extern fn inet_pton(family: c_int, str_ip: ConstStr, net_ip: *anyopaque) c_int; 511 | }; 512 | return raw.inet_pton(family, str_ip, net_ip) == 1; 513 | } 514 | 515 | pub inline fn inet_ntop(family: c_int, net_ip: *const anyopaque, str_buf: *IpStrBuf) ?void { 516 | const raw = struct { 517 | extern fn inet_ntop(family: c_int, net_ip: *const anyopaque, str_buf: [*]u8, str_bufsz: c.socklen_t) ?ConstStr; 518 | }; 519 | return if (raw.inet_ntop(family, net_ip, str_buf, str_buf.len) == null) null; 520 | } 521 | 522 | // ============================================================= 523 | 524 | pub inline fn epoll_create1(flags: c_int) ?c_int { 525 | const raw = struct { 526 | extern fn epoll_create1(flags: c_int) c_int; 527 | }; 528 | const res = raw.epoll_create1(flags); 529 | return if (res >= 0) res else null; 530 | } 531 | 532 | pub inline fn epoll_ctl(epfd: c_int, op: c_int, fd: c_int, ev: ?*anyopaque) ?void { 533 | const raw = struct { 534 | extern fn epoll_ctl(epfd: c_int, op: c_int, fd: c_int, ev: ?*anyopaque) c_int; 535 | }; 536 | return if (raw.epoll_ctl(epfd, op, fd, ev) == -1) null; 537 | } 538 | 539 | pub inline fn epoll_wait(epfd: c_int, evs: *anyopaque, n_evs: c_int, timeout: c_int) ?c_int { 540 | const raw = struct { 541 | extern fn epoll_wait(epfd: c_int, evs: *anyopaque, n_evs: c_int, timeout: c_int) c_int; 542 | }; 543 | const res = raw.epoll_wait(epfd, evs, n_evs, timeout); 544 | return if (res >= 0) res else null; 545 | } 546 | 547 | // ============================================================== 548 | 549 | /// SIG_DFL may have address 0 550 | pub const sighandler_t = ?std.meta.FnPtr(fn (sig: c_int) callconv(.C) void); 551 | 552 | pub inline fn signal(sig: c_int, handler: sighandler_t) ?void { 553 | const raw = struct { 554 | extern fn signal(sig: c_int, handler: sighandler_t) sighandler_t; 555 | }; 556 | return if (raw.signal(sig, handler) == SIG_ERR()) null; 557 | } 558 | 559 | pub inline fn SIG_DFL() sighandler_t { 560 | return @ptrCast(sighandler_t, c.SIG_DEFAULT()); 561 | } 562 | 563 | pub inline fn SIG_IGN() sighandler_t { 564 | return @ptrCast(sighandler_t, c.SIG_IGNORE()); 565 | } 566 | 567 | inline fn SIG_ERR() sighandler_t { 568 | return @ptrCast(sighandler_t, c.SIG_ERROR()); 569 | } 570 | 571 | // ============================================================== 572 | 573 | pub const IpStrBuf = [c.INET6_ADDRSTRLEN - 1:0]u8; 574 | pub const IpNetBuf = [c.IPV6_LEN]u8; 575 | 576 | pub fn ip_to_net(ip: ConstStr, buf: *IpNetBuf) ?[]u8 { 577 | if (inet_pton(c.AF_INET, ip, buf)) 578 | return buf[0..c.IPV4_LEN]; 579 | if (inet_pton(c.AF_INET6, ip, buf)) 580 | return buf[0..c.IPV6_LEN]; 581 | return null; 582 | } 583 | 584 | pub fn ip_family(ip: ConstStr) ?c.sa_family_t { 585 | var buf: IpNetBuf = undefined; 586 | const net_ip = ip_to_net(ip, &buf) orelse return null; 587 | return if (net_ip.len == c.IPV4_LEN) c.AF_INET else c.AF_INET6; 588 | } 589 | 590 | // ============================================================== 591 | 592 | pub const SockAddr = extern union { 593 | sa: c.struct_sockaddr, 594 | sin: c.struct_sockaddr_in, 595 | sin6: c.struct_sockaddr_in6, 596 | 597 | pub inline fn family(self: *const SockAddr) c.sa_family_t { 598 | return self.sa.sa_family; 599 | } 600 | 601 | /// sizeof(sin) or sizeof(sin6) 602 | pub inline fn len(self: *const SockAddr) c.socklen_t { 603 | assert(self.is_sin() or self.is_sin6()); 604 | return if (self.is_sin()) 605 | @sizeOf(c.struct_sockaddr_in) 606 | else 607 | @sizeOf(c.struct_sockaddr_in6); 608 | } 609 | 610 | pub inline fn is_sin(self: *const SockAddr) bool { 611 | return self.family() == c.AF_INET; 612 | } 613 | 614 | pub inline fn is_sin6(self: *const SockAddr) bool { 615 | return self.family() == c.AF_INET6; 616 | } 617 | 618 | pub fn eql(self: *const SockAddr, other: *const SockAddr) bool { 619 | const af = self.family(); 620 | return af == other.family() and switch (af) { 621 | c.AF_INET => std.mem.eql(u8, std.mem.asBytes(&self.sin), std.mem.asBytes(&other.sin)), 622 | c.AF_INET6 => std.mem.eql(u8, std.mem.asBytes(&self.sin6), std.mem.asBytes(&other.sin6)), 623 | else => false, 624 | }; 625 | } 626 | 627 | /// assuming the `ip` and `port` are valid 628 | pub fn from_text(ip: ConstStr, port: u16) SockAddr { 629 | var self: SockAddr = undefined; 630 | @memset(std.mem.asBytes(&self), 0, @sizeOf(SockAddr)); 631 | 632 | if (ip_family(ip).? == c.AF_INET) { 633 | const sin = &self.sin; 634 | sin.sin_family = c.AF_INET; 635 | assert(inet_pton(c.AF_INET, ip, &sin.sin_addr)); 636 | sin.sin_port = htons(port); 637 | } else { 638 | const sin6 = &self.sin6; 639 | sin6.sin6_family = c.AF_INET6; 640 | assert(inet_pton(c.AF_INET6, ip, &sin6.sin6_addr)); 641 | sin6.sin6_port = htons(port); 642 | } 643 | 644 | return self; 645 | } 646 | 647 | pub fn to_text(self: *const SockAddr, ip: *IpStrBuf, port: *u16) void { 648 | if (self.is_sin()) { 649 | const sin = &self.sin; 650 | assert(inet_ntop(c.AF_INET, &sin.sin_addr, ip) != null); 651 | port.* = ntohs(sin.sin_port); 652 | } else { 653 | assert(self.is_sin6()); 654 | const sin6 = &self.sin6; 655 | assert(inet_ntop(c.AF_INET6, &sin6.sin6_addr, ip) != null); 656 | port.* = ntohs(sin6.sin6_port); 657 | } 658 | } 659 | }; 660 | 661 | // ============================================================== 662 | 663 | pub const iovec_t = extern struct { 664 | iov_base: [*]u8, 665 | iov_len: usize, 666 | }; 667 | 668 | pub const msghdr_t = extern struct { 669 | msg_name: ?*SockAddr = null, 670 | msg_namelen: c.socklen_t = 0, 671 | msg_iov: [*]iovec_t, 672 | msg_iovlen: usize, 673 | msg_control: ?[*]u8 = null, 674 | msg_controllen: usize = 0, 675 | msg_flags: c_int = 0, 676 | 677 | pub fn iov_items(self: *const msghdr_t) []iovec_t { 678 | return self.msg_iov[0..self.msg_iovlen]; 679 | } 680 | 681 | /// data length 682 | pub fn calc_len(self: *const msghdr_t) usize { 683 | var len: usize = 0; 684 | for (self.iov_items()) |*iov| 685 | len += iov.iov_len; 686 | return len; 687 | } 688 | 689 | /// for sendmsg 690 | pub fn skip_iov(self: *const msghdr_t, skip_len: usize) void { 691 | assert(skip_len > 0); 692 | var remain_skip = skip_len; 693 | for (self.iov_items()) |*iov| { 694 | if (iov.iov_len == 0) continue; 695 | const n = std.math.min(iov.iov_len, remain_skip); 696 | iov.iov_base += n; 697 | iov.iov_len -= n; 698 | remain_skip -= n; 699 | if (remain_skip == 0) return; 700 | } 701 | unreachable; 702 | } 703 | }; 704 | 705 | pub const mmsghdr_t = extern struct { 706 | msg_hdr: msghdr_t, 707 | msg_len: c_uint = undefined, // return value of recvmsg/sendmsg 708 | }; 709 | 710 | // =============================================================== 711 | 712 | pub inline fn recvmsg(fd: c_int, msg: *msghdr_t, flags: c_int) ?usize { 713 | const n = c.RECVMSG(fd, @ptrCast(*c.MSGHDR, msg), flags); 714 | return if (n >= 0) to_usize(n) else null; 715 | } 716 | 717 | pub inline fn sendmsg(fd: c_int, msg: *const msghdr_t, flags: c_int) ?usize { 718 | const n = c.SENDMSG(fd, @ptrCast(*const c.MSGHDR, msg), flags); 719 | return if (n >= 0) to_usize(n) else null; 720 | } 721 | 722 | // =============================================================== 723 | 724 | extern var RECVMMSG: std.meta.FnPtr(fn (fd: c_int, vec: *anyopaque, vlen: c_uint, flags: c_int, timeout: ?*c.struct_timespec) callconv(.C) c_int); 725 | extern var SENDMMSG: std.meta.FnPtr(fn (fd: c_int, vec: *anyopaque, vlen: c_uint, flags: c_int) callconv(.C) c_int); 726 | 727 | pub inline fn recvmmsg(fd: c_int, msgs: []mmsghdr_t, flags: c_int) ?[]mmsghdr_t { 728 | assert(msgs.len > 0); 729 | const n = RECVMMSG(fd, msgs.ptr, to_uint(msgs.len), flags, null); 730 | return if (n > 0) msgs[0..to_usize(n)] else null; 731 | } 732 | 733 | pub inline fn sendmmsg(fd: c_int, msgs: []mmsghdr_t, flags: c_int) ?[]mmsghdr_t { 734 | assert(msgs.len > 0); 735 | const n = SENDMMSG(fd, msgs.ptr, to_uint(msgs.len), flags); 736 | return if (n > 0) msgs[0..to_usize(n)] else null; 737 | } 738 | 739 | // ============================================================== 740 | 741 | pub inline fn mmap(addr: ?*const anyopaque, len: usize, prot: c_int, flags: c_int, fd: c_int, offset: c.off_t) ?[]u8 { 742 | const raw = struct { 743 | extern fn mmap(addr: ?*const anyopaque, len: usize, prot: c_int, flags: c_int, fd: c_int, offset: c.off_t) [*]u8; 744 | }; 745 | const mem = raw.mmap(addr, len, prot, flags, fd, offset); 746 | return if (mem != @ptrCast([*]u8, c.MAP_FAILED)) 747 | mem[0..len] 748 | else 749 | null; 750 | } 751 | 752 | pub inline fn munmap(mem: []const u8) ?void { 753 | const raw = struct { 754 | extern fn munmap(addr: *const anyopaque, len: usize) c_int; 755 | }; 756 | return if (raw.munmap(mem.ptr, mem.len) == -1) null; 757 | } 758 | 759 | /// mmap a file to memory (readonly) 760 | pub fn mmap_file(filename: ConstStr) ?[]const u8 { 761 | const fd = open(filename, c.O_RDONLY | c.O_CLOEXEC, null) orelse return null; 762 | defer _ = close(fd); 763 | 764 | const size = fstat_size(fd) orelse return null; 765 | 766 | return mmap(null, size, c.PROT_READ, c.MAP_PRIVATE, fd, 0); 767 | } 768 | 769 | // ============================================================== 770 | 771 | /// Initializes the wolfSSL library for use. \ 772 | /// Must be called once per application and before any other call to the library. 773 | pub fn SSL_library_init() void { 774 | assert(c.wolfSSL_Init() == c.WOLFSSL_SUCCESS); 775 | } 776 | 777 | /// the returned string is a pointer to the static buffer 778 | pub fn SSL_error_string(err: c_int) ConstStr { 779 | return switch (err) { 780 | c.WOLFSSL_ERROR_SYSCALL => c.strerror(errno()), 781 | else => c.wolfSSL_ERR_error_string(@bitCast(c_uint, err), null), 782 | }; 783 | } 784 | 785 | /// client-side only 786 | pub fn SSL_CTX_new() *c.WOLFSSL_CTX { 787 | const ctx = c.wolfSSL_CTX_new(c.wolfTLS_client_method()).?; 788 | 789 | // tls12 + tls13 790 | assert(c.wolfSSL_CTX_SetMinVersion(ctx, c.WOLFSSL_TLSV1_2) == 1); 791 | 792 | // cipher list 793 | // openssl has a separate API for tls13, but wolfssl only has one 794 | const chacha20 = "TLS_CHACHA20_POLY1305_SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305"; 795 | const aes128gcm = "TLS_AES_128_GCM_SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256"; 796 | const cipher_list = if (c.has_aes()) aes128gcm ++ ":" ++ chacha20 else chacha20 ++ ":" ++ aes128gcm; 797 | assert(c.wolfSSL_CTX_set_cipher_list(ctx, cipher_list) == 1); 798 | 799 | // options 800 | _ = c.wolfSSL_CTX_set_options(ctx, c.WOLFSSL_OP_NO_COMPRESSION | c.WOLFSSL_OP_NO_RENEGOTIATION); 801 | 802 | return ctx; 803 | } 804 | 805 | pub fn SSL_CTX_load_CA_certs(ctx: *c.WOLFSSL_CTX, path: ConstStr) ?void { 806 | const ok = if (is_dir(path)) 807 | c.wolfSSL_CTX_load_verify_locations(ctx, null, path) 808 | else // file 809 | c.wolfSSL_CTX_load_verify_locations(ctx, path, null); 810 | return if (ok == 1) {} else null; 811 | } 812 | 813 | pub fn SSL_CTX_load_sys_CA_certs(ctx: *c.WOLFSSL_CTX) ?void { 814 | return if (c.wolfSSL_CTX_load_system_CA_certs(ctx) == 1) {} else null; 815 | } 816 | 817 | pub fn SSL_new(ctx: *c.WOLFSSL_CTX) *c.WOLFSSL { 818 | return c.wolfSSL_new(ctx).?; 819 | } 820 | 821 | pub fn SSL_free(ssl: *c.WOLFSSL) void { 822 | return c.wolfSSL_free(ssl); 823 | } 824 | 825 | pub fn SSL_set_fd(ssl: *c.WOLFSSL, fd: c_int) ?void { 826 | return if (c.wolfSSL_set_fd(ssl, fd) == 1) {} else null; 827 | } 828 | 829 | /// set SNI && enable hostname validation during SSL handshake 830 | pub fn SSL_set_host(ssl: *c.WOLFSSL, host: ?ConstStr, cert_verify: bool) ?void { 831 | if (host) |name| { 832 | // tls_ext: SNI (ClientHello) 833 | if (c.wolfSSL_UseSNI(ssl, c.WOLFSSL_SNI_HOST_NAME, name, to_ushort(c.strlen(name))) != 1) 834 | return null; 835 | 836 | // check hostname on ssl cert validation 837 | if (cert_verify and c.wolfSSL_check_domain_name(ssl, name) != 1) 838 | return null; 839 | } 840 | 841 | // ssl cert validation 842 | const mode = if (cert_verify) c.WOLFSSL_VERIFY_PEER else c.WOLFSSL_VERIFY_NONE; 843 | c.wolfSSL_set_verify(ssl, mode, null); 844 | } 845 | 846 | /// set session to be used when the TLS/SSL connection is to be established (resumption) \ 847 | /// when the session is set, the reference count of session is incremented by 1 (owner by ssl) \ 848 | /// after session is set, wolfSSL_SESSION_free() can be called to dereference it (release ownership) 849 | pub fn SSL_set_session(ssl: *c.WOLFSSL, session: *c.WOLFSSL_SESSION) void { 850 | // may fail due to session timeout, don't care about it 851 | _ = c.wolfSSL_set_session(ssl, session); 852 | } 853 | 854 | /// for SSL I/O operation 855 | fn SSL_get_error(ssl: *c.WOLFSSL, res: c_int) c_int { 856 | var err = c.wolfSSL_get_error(ssl, res); 857 | if (err == c.SOCKET_PEER_CLOSED_E or err == c.SOCKET_ERROR_E) 858 | // convert to socket error (errno) 859 | err = if (errno() == 0) // TCP EOF 860 | c.WOLFSSL_ERROR_ZERO_RETURN 861 | else 862 | c.WOLFSSL_ERROR_SYSCALL; 863 | return err; 864 | } 865 | 866 | /// perform SSL/TLS handshake (underlying transport is established) \ 867 | /// `p_err`: to save the failure reason (SSL_ERROR_*) 868 | pub fn SSL_connect(ssl: *c.WOLFSSL, p_err: *c_int) ?void { 869 | const res = c.wolfSSL_connect(ssl); 870 | if (res == 1) { 871 | return {}; 872 | } else { 873 | p_err.* = SSL_get_error(ssl, res); 874 | return null; 875 | } 876 | } 877 | 878 | /// the name of the protocol used for the connection 879 | pub fn SSL_get_version(ssl: *const c.WOLFSSL) ConstStr { 880 | return c.wolfSSL_get_version(ssl); 881 | } 882 | 883 | /// the name of the cipher used for the connection 884 | pub fn SSL_get_cipher(ssl: *c.WOLFSSL) ConstStr { 885 | return c.wolfSSL_get_cipher(ssl) orelse "NULL"; 886 | } 887 | 888 | /// queries whether session resumption occurred during the handshake 889 | pub fn SSL_session_reused(ssl: *c.WOLFSSL) bool { 890 | return c.wolfSSL_session_reused(ssl) != 0; 891 | } 892 | 893 | /// return the number of bytes read (> 0) \ 894 | /// `p_err`: to save the failure reason (SSL_ERROR_*) 895 | pub fn SSL_read(ssl: *c.WOLFSSL, buf: []u8, p_err: *c_int) ?usize { 896 | const res = c.wolfSSL_read(ssl, buf.ptr, to_int(buf.len)); 897 | if (res > 0) { 898 | return to_usize(res); 899 | } else { 900 | p_err.* = SSL_get_error(ssl, res); 901 | return null; 902 | } 903 | } 904 | 905 | /// assume SSL_MODE_ENABLE_PARTIAL_WRITE is not in use \ 906 | /// `p_err`: to save the failure reason (SSL_ERROR_*) 907 | pub fn SSL_write(ssl: *c.WOLFSSL, buf: []const u8, p_err: *c_int) ?void { 908 | const res = c.wolfSSL_write(ssl, buf.ptr, to_int(buf.len)); 909 | if (res > 0) { 910 | return {}; 911 | } else { 912 | p_err.* = SSL_get_error(ssl, res); 913 | return null; 914 | } 915 | } 916 | 917 | /// the reference count of the SSL_SESSION is incremented by one \ 918 | /// in TLSv1.3 it is recommended that each SSL_SESSION object is only used for resumption once 919 | pub fn SSL_get1_session(ssl: *c.WOLFSSL) ?*c.WOLFSSL_SESSION { 920 | return c.wolfSSL_get1_session(ssl); 921 | } 922 | 923 | pub fn SSL_SESSION_free(session: *c.WOLFSSL_SESSION) void { 924 | return c.wolfSSL_SESSION_free(session); 925 | } 926 | 927 | // ============================================================== 928 | 929 | pub fn @"test: strslice"() !void { 930 | const hello = "hello"; 931 | const N = hello.len; 932 | 933 | const slice = strslice(hello); 934 | try testing.expectEqual([:0]const u8, @TypeOf(slice)); 935 | try testing.expectEqualStrings(hello, slice); 936 | try testing.expectEqual(hello.len, slice.len); 937 | try testing.expectEqual(hello.len, std.mem.indexOfSentinel(u8, 0, slice)); 938 | 939 | const const_buf: [N]u8 = hello.*; 940 | try testing.expectEqual([]const u8, @TypeOf(strslice(&const_buf))); 941 | 942 | const const_buf_z: [N:0]u8 = hello.*; 943 | try testing.expectEqual([:0]const u8, @TypeOf(strslice(&const_buf_z))); 944 | 945 | var var_buf: [N]u8 = hello.*; 946 | try testing.expectEqual([]u8, @TypeOf(strslice(&var_buf))); 947 | 948 | var var_buf_z: [N:0]u8 = hello.*; 949 | try testing.expectEqual([:0]u8, @TypeOf(strslice(&var_buf_z))); 950 | } 951 | 952 | pub fn @"test: strslice_c"() !void { 953 | const hello = "hello"; 954 | const N = hello.len; 955 | 956 | const slice = strslice_c(hello); 957 | try testing.expectEqual([:0]const u8, @TypeOf(slice)); 958 | try testing.expectEqualStrings(hello, slice); 959 | try testing.expectEqual(hello.len, slice.len); 960 | try testing.expectEqual(hello.len, std.mem.indexOfSentinel(u8, 0, slice)); 961 | 962 | const const_buf: [N]u8 = hello.*; 963 | try testing.expectEqual([]const u8, @TypeOf(strslice_c(&const_buf))); 964 | 965 | const const_buf_z: [N:0]u8 = hello.*; 966 | try testing.expectEqual([:0]const u8, @TypeOf(strslice_c(&const_buf_z))); 967 | 968 | var var_buf: [N]u8 = hello.*; 969 | try testing.expectEqual([]const u8, @TypeOf(strslice_c(&var_buf))); 970 | 971 | var var_buf_z: [N:0]u8 = hello.*; 972 | try testing.expectEqual([:0]const u8, @TypeOf(strslice_c(&var_buf_z))); 973 | } 974 | 975 | pub fn @"test: errno"() !void { 976 | set_errno(c.EAGAIN); 977 | try testing.expectEqual(c.EAGAIN, errno()); 978 | } 979 | 980 | pub fn @"test: snprintf"() !void { 981 | var buffer: [11]u8 = undefined; 982 | const helloworld = "helloworld"; 983 | const str = snprintf(&buffer, "%s", .{helloworld}); 984 | try testing.expect(helloworld.len == 10); 985 | try testing.expectEqual(helloworld.len, str.len); 986 | try testing.expectEqualStrings(helloworld, str); 987 | try testing.expectEqualSentinel(u8, 0, helloworld, str); 988 | } 989 | 990 | pub fn @"test: snprintf overflow"() !void { 991 | var buffer: [10]u8 = undefined; 992 | const helloworld = "helloworld"; 993 | const str = snprintf(&buffer, "%s", .{helloworld}); 994 | try testing.expectEqual(@as(usize, 9), str.len); 995 | try testing.expectEqualSlices(u8, helloworld[0..9], str); 996 | try testing.expectEqualStrings(helloworld[0..9 :'d'], str); 997 | try testing.expectEqual(@as(usize, 9), std.mem.indexOfSentinel(u8, 0, str)); 998 | } 999 | -------------------------------------------------------------------------------- /src/cfg.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const c = @import("c.zig"); 3 | const cc = @import("cc.zig"); 4 | const g = @import("g.zig"); 5 | const in = @import("in.zig"); 6 | const out = @import("out.zig"); 7 | const Config = @import("Config.zig"); 8 | const log = @import("log.zig"); 9 | const str2int = @import("str2int.zig"); 10 | const flags_op = @import("flags_op.zig"); 11 | const assert = std.debug.assert; 12 | 13 | const SectionType = enum { global, in, out }; 14 | 15 | fn load_config(start: [*]const u8, end: [*]const u8, section_type: SectionType, proto_name: []const u8) void { 16 | const section = start[0..cc.ptrdiff_u(u8, end, start)]; 17 | switch (section_type) { 18 | .global => { 19 | g.config = Config.load(section) orelse cc.exit(1); 20 | }, 21 | .in => { 22 | g.in_config = in.load_config(proto_name, section) orelse cc.exit(1); 23 | }, 24 | .out => { 25 | g.out_config = out.load_config(proto_name, section) orelse cc.exit(1); 26 | }, 27 | } 28 | } 29 | 30 | pub fn load(filename: cc.ConstStr) void { 31 | const src = @src(); 32 | 33 | const mem = cc.mmap_file(filename) orelse { 34 | log.err(src, "failed to open file: '%s' (%m)", .{filename}); 35 | cc.exit(1); 36 | }; 37 | defer _ = cc.munmap(mem); 38 | 39 | const Loaded = enum(u8) { 40 | global = 1 << 0, 41 | in = 1 << 1, 42 | out = 1 << 2, 43 | _, 44 | pub usingnamespace flags_op.get(@This()); 45 | }; 46 | var loaded: Loaded = Loaded.empty(); 47 | defer { 48 | if (!loaded.has_all(.{ .in, .out })) { 49 | const missing = cc.b2s(!loaded.has(.in), "in", "out"); 50 | log.err(src, "missing %s.proto config", .{missing}); 51 | cc.exit(1); 52 | } 53 | if (!loaded.has(.global)) 54 | g.config = Config.load("").?; 55 | } 56 | 57 | var section_start: ?[*]const u8 = null; // the start pos of the content 58 | var section_type: SectionType = undefined; 59 | var proto_name: []const u8 = undefined; // in or out 60 | defer if (section_start) |start| 61 | load_config(start, mem.ptr + mem.len, section_type, proto_name); 62 | 63 | var line_it = std.mem.tokenize(u8, mem, "\r\n"); 64 | while (line_it.next()) |line| { 65 | const err: cc.ConstStr = e: { 66 | var it = std.mem.tokenize(u8, line, " \t"); 67 | 68 | // # comments 69 | // [global] # comments 70 | // [in.tproxy] 71 | // [out.socks] 72 | // name = value # comments 73 | // name = "string" 74 | const token = it.next() orelse continue; 75 | 76 | switch (token[0]) { 77 | '#' => continue, 78 | 79 | '[' => { 80 | if (token[token.len - 1] != ']') 81 | break :e "invalid format"; 82 | 83 | const rest = it.rest(); 84 | if (rest.len > 0 and rest[0] != '#') 85 | break :e "invalid format"; 86 | 87 | // handle the last section 88 | if (section_start) |start| 89 | load_config(start, token.ptr, section_type, proto_name); 90 | 91 | // advance to the next section 92 | section_start = token.ptr + token.len; 93 | if (std.mem.eql(u8, token, "[global]")) { 94 | section_type = .global; 95 | proto_name = undefined; 96 | if (loaded.has(.global)) break :e "duplicate global section"; 97 | loaded.add(.global); 98 | } else if (std.mem.startsWith(u8, token, "[in.")) { 99 | section_type = .in; 100 | proto_name = token[4 .. token.len - 1]; 101 | if (loaded.has(.in)) break :e "duplicate in.proto section"; 102 | loaded.add(.in); 103 | } else if (std.mem.startsWith(u8, token, "[out.")) { 104 | section_type = .out; 105 | proto_name = token[5 .. token.len - 1]; 106 | if (loaded.has(.out)) break :e "duplicate out.proto section"; 107 | loaded.add(.out); 108 | } else { 109 | break :e "unknown section"; 110 | } 111 | }, 112 | 113 | else => if (section_start == null) break :e "out of section", 114 | } 115 | 116 | continue; 117 | }; 118 | 119 | // error handling 120 | log.err(src, "'%s': %s: %.*s", .{ filename, err, cc.to_int(line.len), line.ptr }); 121 | cc.exit(1); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/cfg_checker.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const cc = @import("cc.zig"); 3 | const log = @import("log.zig"); 4 | const SourceLocation = std.builtin.SourceLocation; 5 | 6 | pub fn required(comptime src: SourceLocation, exists: bool, name: cc.ConstStr) ?void { 7 | if (!exists) { 8 | log.err(src, "missing config: '%s'", .{name}); 9 | return null; 10 | } 11 | } 12 | 13 | pub fn check_ip(comptime src: SourceLocation, ip: [:0]const u8) ?void { 14 | if (cc.ip_family(ip) == null) { 15 | log.err(src, "invalid ip: '%s'", .{ip.ptr}); 16 | return null; 17 | } 18 | } 19 | 20 | pub fn check_ips(comptime src: SourceLocation, ips: []const [:0]const u8) ?void { 21 | for (ips) |ip| 22 | check_ip(src, ip) orelse return null; 23 | } 24 | 25 | pub fn check_port(comptime src: SourceLocation, port: u16) ?void { 26 | if (port == 0) { 27 | log.err(src, "invalid port: %u", .{cc.to_uint(port)}); 28 | return null; 29 | } 30 | } 31 | 32 | pub fn check_tcp_udp(comptime src: SourceLocation, tcp: bool, udp: bool) ?void { 33 | if (!tcp and !udp) { 34 | log.err(src, "both tcp and udp are disabled", .{}); 35 | return null; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/cfg_loader.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const cc = @import("cc.zig"); 3 | const g = @import("g.zig"); 4 | const log = @import("log.zig"); 5 | const str2int = @import("str2int.zig"); 6 | const SourceLocation = std.builtin.SourceLocation; 7 | const assert = std.debug.assert; 8 | 9 | pub const EMPTY_STR = &[0:0]u8{}; 10 | 11 | /// ```zig 12 | /// Config = struct { 13 | /// ip: [][:0]const u8 = &.{}, 14 | /// port: u16 = 1080, 15 | /// udp: bool = true, 16 | /// } 17 | /// content = \\ ip = 127.0.0.1 # foo 18 | /// \\ ip = 192.168.1.1 # bar 19 | /// \\ port = 1080 20 | /// \\ udp = true 21 | /// \\ # comments 22 | /// ``` 23 | pub fn load(comptime src: SourceLocation, config: anytype, content: []const u8) ?void { 24 | const ptrinfo = @typeInfo(@TypeOf(config)); 25 | if (ptrinfo != .Pointer or @typeInfo(ptrinfo.Pointer.child) != .Struct) 26 | @compileError("expect struct pointer, got " ++ @typeName(@TypeOf(config))); 27 | const Config = ptrinfo.Pointer.child; 28 | 29 | var line_it = std.mem.tokenize(u8, content, "\r\n"); 30 | while (line_it.next()) |raw_line| { 31 | const err: cc.ConstStr = e: { 32 | const line = trim_space(raw_line); 33 | if (line[0] == '#') continue; 34 | 35 | const sep = std.mem.indexOfScalar(u8, line, '=') orelse break :e "invalid format"; 36 | const name = trim_space(line[0..sep]); 37 | const value = trim_space(trim_comment(line[sep + 1 ..])); 38 | if (value.len == 0) break :e "missing value"; 39 | 40 | if (load_field(Config, config, name, value)) |err| 41 | break :e err; 42 | 43 | continue; 44 | }; 45 | 46 | log.err(src, "%s: '%.*s'", .{ err, cc.to_int(raw_line.len), raw_line.ptr }); 47 | return null; 48 | } 49 | } 50 | 51 | fn trim_space(s: []const u8) []const u8 { 52 | return std.mem.trim(u8, s, " \t"); 53 | } 54 | 55 | fn trim_comment(s: []const u8) []const u8 { 56 | const end = std.mem.indexOf(u8, s, " #") orelse 57 | std.mem.indexOf(u8, s, "\t#") orelse s.len; 58 | return s[0..end]; 59 | } 60 | 61 | /// return error msg if failed 62 | /// https://github.com/ziglang/zig/issues/11369 63 | fn load_field(comptime Config: type, config: *Config, name: []const u8, value: []const u8) ?cc.ConstStr { 64 | inline for (std.meta.fields(Config)) |field| { 65 | if (std.mem.eql(u8, field.name, name)) { 66 | const p_field = &@field(config, field.name); 67 | p_field.* = parse_value(Config, field, p_field.*, value) orelse return "invalid value"; 68 | return null; // no error 69 | } 70 | } 71 | return "unknown config"; 72 | } 73 | 74 | /// return new value (or null if failed) 75 | fn parse_value( 76 | comptime Config: type, 77 | comptime field: std.builtin.Type.StructField, 78 | old_value: field.field_type, 79 | cfg_value: []const u8, 80 | ) ?field.field_type { 81 | assert(cfg_value.len > 0); 82 | 83 | const field_name = @typeName(Config) ++ "." ++ field.name; 84 | const field_type = field.field_type; 85 | const field_typename = @typeName(field_type); 86 | const p_default_value = field.default_value orelse 87 | @compileError("field must have default value: " ++ field_name); 88 | 89 | return switch (@typeInfo(field_type)) { 90 | .Bool => parse_bool(cfg_value), 91 | 92 | .Int => parse_int(field_type, cfg_value), 93 | 94 | .Pointer => |info| b: { 95 | if (info.size != .Slice) 96 | @compileError("non-slice pointer not supported: " ++ field_typename ++ ", field: " ++ field_name); 97 | 98 | // check the length of the slice object 99 | const slice_len = @ptrCast(*const []u8, p_default_value).len; 100 | if (slice_len != 0) 101 | @compileError("the default value of the dyn-alloc field must be empty: " ++ field_name ++ ", len: " ++ cc.comptime_tostr(slice_len)); 102 | 103 | if (info.sentinel) |p_sentinel| { 104 | // string: [:0]u8, [:0]const u8 105 | if (info.child != u8) 106 | @compileError("sentinel-terminated slice not supported: " ++ field_typename ++ ", field: " ++ field_name); 107 | 108 | if (@ptrCast(*const u8, p_sentinel).* != 0) 109 | @compileError("string field must end with sentinel 0: " ++ field_typename ++ ", field: " ++ field_name); 110 | 111 | break :b parse_str(old_value, cfg_value); 112 | } else { 113 | // slice: []T, []const T 114 | break :b parse_slice(info.child, old_value, cfg_value); 115 | } 116 | }, 117 | 118 | else => @compileError("expect {bool, int, string, and their slice}, got " ++ field_typename ++ ", field: " ++ field_name), 119 | }; 120 | } 121 | 122 | /// ignore duplicate elements (so it's actually a hashset) 123 | fn parse_slice(comptime T: type, old_slice: []const T, cfg_value: []const u8) ?[]T { 124 | comptime var is_string = false; 125 | 126 | const value_to_add = (switch (@typeInfo(T)) { 127 | .Bool => parse_bool(cfg_value), 128 | .Int => parse_int(T, cfg_value), 129 | .Pointer => b: { 130 | // string 131 | if (T != [:0]const u8 and T != [:0]u8) 132 | @compileError("expect {bool, int, string}, got " ++ @typeName(T)); 133 | is_string = true; 134 | break :b @as(?[]const u8, cfg_value); 135 | }, 136 | else => @compileError("expect {bool, int, string}, got " ++ @typeName(T)), 137 | }) orelse return null; 138 | 139 | // check for duplicate 140 | for (old_slice) |value| { 141 | if (is_string) { 142 | if (std.mem.eql(u8, value_to_add, value)) 143 | return cc.remove_const(old_slice); 144 | } else { 145 | if (value_to_add == value) 146 | return cc.remove_const(old_slice); 147 | } 148 | } 149 | 150 | const new_slice = if (old_slice.len == 0) 151 | g.allocator.alloc(T, 1) catch unreachable 152 | else 153 | g.allocator.realloc(cc.remove_const(old_slice), old_slice.len + 1) catch unreachable; 154 | 155 | new_slice[old_slice.len] = if (is_string) 156 | (g.allocator.dupeZ(u8, value_to_add) catch unreachable) 157 | else 158 | value_to_add; 159 | 160 | return new_slice; 161 | } 162 | 163 | fn parse_bool(cfg_value: []const u8) ?bool { 164 | if (std.mem.eql(u8, cfg_value, "true")) 165 | return true; 166 | if (std.mem.eql(u8, cfg_value, "false")) 167 | return false; 168 | return null; 169 | } 170 | 171 | fn parse_int(comptime T: type, cfg_value: []const u8) ?T { 172 | return str2int.parse(T, cfg_value, 10); 173 | } 174 | 175 | fn parse_str(in_old_str: [:0]const u8, cfg_value: []const u8) ?[:0]u8 { 176 | const old_str = cc.remove_const(in_old_str); 177 | assert(cfg_value.len > 0); 178 | 179 | // replace old content 180 | const new_str = if (old_str.len > 0 and g.allocator.resize(old_str.ptr[0 .. old_str.len + 1], cfg_value.len + 1) != null) 181 | old_str.ptr[0 .. cfg_value.len + 1] 182 | else b: { 183 | if (old_str.len > 0) g.allocator.free(old_str); 184 | break :b g.allocator.alloc(u8, cfg_value.len + 1) catch unreachable; 185 | }; 186 | 187 | @memcpy(new_str.ptr, cfg_value.ptr, cfg_value.len); 188 | new_str[cfg_value.len] = 0; 189 | 190 | return new_str[0..cfg_value.len :0]; 191 | } 192 | -------------------------------------------------------------------------------- /src/co.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const cc = @import("cc.zig"); 3 | const g = @import("g.zig"); 4 | const assert = std.debug.assert; 5 | 6 | /// create and start a new coroutine 7 | pub fn create(comptime func: anytype, args: anytype) void { 8 | const buf = g.allocator.alignedAlloc(u8, std.Target.stack_align, @frameSize(func)) catch unreachable; 9 | _ = @asyncCall(buf, {}, func, args); 10 | // @call(.{ .modifier = .async_kw, .stack = buf }, func, args); 11 | check_terminated(); 12 | } 13 | 14 | /// if the coroutine is at the last pause point, its memory will be freed after resume 15 | pub fn do_resume(frame: anyframe) void { 16 | resume frame; 17 | check_terminated(); 18 | } 19 | 20 | /// called when the coroutine is about to terminate: `defer co.terminate(@frame(), frame_size)` 21 | pub fn terminate(top_frame: anyframe, frame_size: usize) void { 22 | assert(_terminated == null); 23 | _terminated = top_frame; 24 | _frame_size = frame_size; 25 | } 26 | 27 | var _terminated: ?anyframe = null; 28 | var _frame_size: usize = 0; 29 | 30 | /// free the memory of a terminated coroutine 31 | fn check_terminated() void { 32 | const top_frame = _terminated orelse return; 33 | _terminated = null; 34 | 35 | // https://github.com/ziglang/zig/issues/10622 36 | const ptr = @intToPtr([*]u8, @ptrToInt(top_frame)); 37 | return g.allocator.free(ptr[0.._frame_size]); 38 | } 39 | -------------------------------------------------------------------------------- /src/flags_op.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const assert = std.debug.assert; 3 | 4 | /// ```zig 5 | /// const Flags = enum(u8) { 6 | /// foo = 1 << 0, 7 | /// bar = 1 << 1, 8 | /// xyz = 1 << 2, 9 | /// _, // non-exhaustive enum 10 | /// pub usingnamespace flags_op.get(Flags); 11 | /// }; 12 | /// ``` 13 | pub fn get(comptime Self: type) type { 14 | comptime assert(@typeInfo(Self) == .Enum); 15 | const enum_info = @typeInfo(Self).Enum; 16 | const Int = enum_info.tag_type; 17 | comptime assert(!enum_info.is_exhaustive); 18 | 19 | return struct { 20 | /// ```zig 21 | /// const flags = Flags.init(.{.flag_a, .flag_b, .flag_c}); 22 | /// const flags = Flags.init(0xff); 23 | /// ``` 24 | pub inline fn init(flags: anytype) Self { 25 | switch (@typeInfo(@TypeOf(flags))) { 26 | .Enum, .EnumLiteral => return flags, 27 | .Int, .ComptimeInt => return @intToEnum(Self, flags), 28 | else => comptime { 29 | // .{.flag_a, .flag_b, .flag_c} 30 | var res = empty(); 31 | for (flags) |f| 32 | res.add(f); 33 | return res; 34 | }, 35 | } 36 | } 37 | 38 | pub inline fn empty() Self { 39 | return init(0); 40 | } 41 | 42 | pub inline fn full() Self { 43 | return init(std.math.maxInt(Int)); 44 | } 45 | 46 | // ===================================================== 47 | 48 | pub inline fn int(self: Self) Int { 49 | return @enumToInt(self); 50 | } 51 | 52 | // ===================================================== 53 | 54 | pub inline fn add(self: *Self, in_flags: anytype) void { 55 | const flags = init(in_flags); 56 | self.* = init(self.int() | flags.int()); 57 | } 58 | 59 | pub inline fn rm(self: *Self, in_flags: anytype) void { 60 | const flags = init(in_flags); 61 | self.* = init(self.int() & ~flags.int()); 62 | } 63 | 64 | // ===================================================== 65 | 66 | /// for single flag bit 67 | /// for multiple flag bits, equivalent to `has_all` 68 | pub inline fn has(self: Self, in_flags: anytype) bool { 69 | const flags = init(in_flags); 70 | return self.int() & flags.int() == flags.int(); 71 | } 72 | 73 | /// for multiple flag bits 74 | pub inline fn has_all(self: Self, in_flags: anytype) bool { 75 | return self.has(in_flags); 76 | } 77 | 78 | /// for multiple flag bits 79 | pub inline fn has_any(self: Self, in_flags: anytype) bool { 80 | const flags = init(in_flags); 81 | return self.int() & flags.int() != 0; 82 | } 83 | 84 | // ===================================================== 85 | 86 | pub inline fn is_empty(self: Self) bool { 87 | return self.int() == 0; 88 | } 89 | 90 | pub inline fn is_full(self: Self) bool { 91 | return self.int() == std.math.maxInt(Int); 92 | } 93 | 94 | // ===================================================== 95 | 96 | pub inline fn to_empty(self: *Self) void { 97 | self.* = empty(); 98 | } 99 | 100 | pub inline fn to_full(self: *Self) void { 101 | self.* = full(); 102 | } 103 | }; 104 | } 105 | -------------------------------------------------------------------------------- /src/fmtchk.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const c = @import("c.zig"); 3 | const cc = @import("cc.zig"); 4 | const Type = std.builtin.Type; 5 | const StructField = Type.StructField; 6 | 7 | fn SliceIterator(comptime T: type) type { 8 | return struct { 9 | slice: []const T, // ptr + len 10 | pos: usize = 0, // next pos 11 | 12 | const Self = @This(); 13 | 14 | fn init(comptime slice: []const T) Self { 15 | return .{ .slice = slice }; 16 | } 17 | 18 | fn empty(comptime self: *const Self) bool { 19 | return self.pos >= self.slice.len; 20 | } 21 | 22 | fn peek(comptime self: *const Self) ?T { 23 | if (self.empty()) return null; 24 | return self.slice[self.pos]; 25 | } 26 | 27 | fn peek_force(comptime self: *const Self) T { 28 | if (self.empty()) unreachable; 29 | return self.slice[self.pos]; 30 | } 31 | 32 | fn pop(comptime self: *Self) ?T { 33 | if (self.empty()) return null; 34 | defer self.pos += 1; 35 | return self.slice[self.pos]; 36 | } 37 | 38 | fn pop_force(comptime self: *Self) T { 39 | return self.pop().?; 40 | } 41 | 42 | fn pop_force_void(comptime self: *Self) void { 43 | _ = self.pop_force(); 44 | } 45 | }; 46 | } 47 | 48 | const FormatIterator = SliceIterator(u8); 49 | const ArgsIterator = SliceIterator(StructField); 50 | 51 | // https://en.cppreference.com/w/c/io/fprintf 52 | // https://cplusplus.com/reference/cstdio/printf/ 53 | // https://man7.org/linux/man-pages/man3/printf.3.html 54 | fn parse_flags(comptime format: *FormatIterator) void { 55 | while (true) { 56 | const char = format.peek() orelse return; 57 | switch (char) { 58 | '-', '+', ' ', '#', '0' => format.pop_force_void(), 59 | else => return, 60 | } 61 | } 62 | } 63 | 64 | fn parse_width(comptime format: *FormatIterator, comptime args: *ArgsIterator) void { 65 | const char = format.peek() orelse return; 66 | if (char == '*') { 67 | // width in args 68 | format.pop_force_void(); 69 | const arg = args.pop() orelse @compileError("expect a c_int for width '*'"); 70 | if (arg.field_type != c_int) @compileError("expect a c_int for width '*', but got a " ++ @typeName(arg.field_type)); 71 | } else { 72 | // width in format 73 | while (!format.empty() and '0' <= format.peek_force() and format.peek_force() <= '9') 74 | format.pop_force_void(); 75 | } 76 | } 77 | 78 | /// return true if precision is specified 79 | fn parse_precision(comptime format: *FormatIterator, comptime args: *ArgsIterator) bool { 80 | const char = format.peek() orelse return false; 81 | 82 | if (char == '.') { 83 | format.pop_force_void(); 84 | 85 | // if neither a number nor * is used, the precision is taken as zero 86 | const next_char = format.peek() orelse return true; 87 | 88 | if (next_char == '*') { 89 | // width in args 90 | format.pop_force_void(); 91 | const arg = args.pop() orelse @compileError("expect a c_int for precision '*'"); 92 | if (arg.field_type != c_int) @compileError("expect a c_int for precision '*', but got a " ++ @typeName(arg.field_type)); 93 | } else { 94 | // width in format 95 | while (!format.empty() and '0' <= format.peek_force() and format.peek_force() <= '9') 96 | format.pop_force_void(); 97 | } 98 | 99 | return true; 100 | } 101 | 102 | return false; 103 | } 104 | 105 | const Modifier = enum { 106 | none, 107 | hh, // char 108 | h, // short 109 | l, // long 110 | ll, // long long 111 | j, // max 112 | z, // size 113 | t, // ptrdiff 114 | L, // long double 115 | 116 | fn desc(m: Modifier) [:0]const u8 { 117 | return if (m == .none) "" else @tagName(m); 118 | } 119 | }; 120 | 121 | fn parse_modifier(comptime format: *FormatIterator) Modifier { 122 | const char = format.peek() orelse return .none; 123 | switch (char) { 124 | 'h' => { 125 | format.pop_force_void(); 126 | if (!format.empty() and format.peek_force() == 'h') { 127 | format.pop_force_void(); 128 | return .hh; 129 | } 130 | return .h; 131 | }, 132 | 'l' => { 133 | format.pop_force_void(); 134 | if (!format.empty() and format.peek_force() == 'l') { 135 | format.pop_force_void(); 136 | return .ll; 137 | } 138 | return .l; 139 | }, 140 | 'j' => { 141 | format.pop_force_void(); 142 | return .j; 143 | }, 144 | 'z' => { 145 | format.pop_force_void(); 146 | return .z; 147 | }, 148 | 't' => { 149 | format.pop_force_void(); 150 | return .t; 151 | }, 152 | 'L' => { 153 | format.pop_force_void(); 154 | return .L; 155 | }, 156 | else => return .none, 157 | } 158 | } 159 | 160 | fn parse_specifier(comptime format: *FormatIterator, comptime args: *ArgsIterator, modifier: Modifier, has_precision: bool) void { 161 | const c_float_or_double = struct {}; // float or double 162 | const c_string = struct {}; // const char * 163 | const c_pointer = struct {}; // const void * 164 | 165 | const specifier = format.pop() orelse @compileError("expect a specifier character"); 166 | 167 | const expect_type: type = switch (specifier) { 168 | 'd', 'i' => switch (modifier) { 169 | // signed integer 170 | .none => c_int, 171 | .hh => c.schar, 172 | .h => c_short, 173 | .l => c_long, 174 | .ll => c_longlong, 175 | .j => c.intmax_t, 176 | .z => isize, 177 | .t => c.ptrdiff_t, 178 | else => |m| @compileError("the length modifier '" ++ m.desc() ++ "' cannot be applied to %" ++ [_]u8{specifier}), 179 | }, 180 | 'u', 'o', 'x', 'X' => switch (modifier) { 181 | // unsigned integer 182 | .none => c_uint, 183 | .hh => c.uchar, 184 | .h => c_ushort, 185 | .l => c_ulong, 186 | .ll => c_ulonglong, 187 | .j => c.uintmax_t, 188 | .z => usize, 189 | .t => c.ptrdiff_t, 190 | else => |m| @compileError("the length modifier '" ++ m.desc() ++ "' cannot be applied to %" ++ [_]u8{specifier}), 191 | }, 192 | 'f', 'F', 'e', 'E', 'g', 'G', 'a', 'A' => switch (modifier) { 193 | // floating-point 194 | .none => c_float_or_double, 195 | .L => c_longdouble, 196 | else => |m| @compileError("the length modifier '" ++ m.desc() ++ "' cannot be applied to %" ++ [_]u8{specifier}), 197 | }, 198 | 'c' => switch (modifier) { 199 | // character 200 | .none => c.char, 201 | else => |m| @compileError("the length modifier '" ++ m.desc() ++ "' cannot be applied to %" ++ [_]u8{specifier}), 202 | }, 203 | 's' => switch (modifier) { 204 | // c string 205 | .none => c_string, 206 | else => |m| @compileError("the length modifier '" ++ m.desc() ++ "' cannot be applied to %" ++ [_]u8{specifier}), 207 | }, 208 | 'p' => switch (modifier) { 209 | // void * 210 | .none => c_pointer, 211 | else => |m| @compileError("the length modifier '" ++ m.desc() ++ "' cannot be applied to %" ++ [_]u8{specifier}), 212 | }, 213 | 'n' => switch (modifier) { 214 | // the number of characters written so far is stored into the integer pointed to by the corresponding argument. 215 | .none => *c_int, 216 | .hh => *c.schar, 217 | .h => *c_short, 218 | .l => *c_long, 219 | .ll => *c_longlong, 220 | .j => *c.intmax_t, 221 | .z => *isize, 222 | .t => *c.ptrdiff_t, 223 | else => |m| @compileError("the length modifier '" ++ m.desc() ++ "' cannot be applied to %" ++ [_]u8{specifier}), 224 | }, 225 | 'm' => switch (modifier) { 226 | // glibc extension; supported by uClibc and musl. 227 | // print output of strerror(errno). 228 | // no argument is required. 229 | .none => return, 230 | else => |m| @compileError("the length modifier '" ++ m.desc() ++ "' cannot be applied to %" ++ [_]u8{specifier}), 231 | }, 232 | else => @compileError("invalid specifier character: %" ++ modifier.desc() ++ [_]u8{specifier}), 233 | }; 234 | 235 | const arg = args.pop() orelse @compileError("expect a " ++ @typeName(expect_type) ++ " for specifier %" ++ modifier.desc() ++ [_]u8{specifier}); 236 | const arg_type = arg.field_type; 237 | 238 | // checks the arg_type and return if it is correct 239 | switch (expect_type) { 240 | c_float_or_double => { 241 | if (arg_type == f32 or arg_type == f64) return; 242 | }, 243 | c_string => { 244 | const info = @typeInfo(arg_type); 245 | if (info == .Pointer) switch (info.Pointer.size) { 246 | .One => { 247 | // pointer to c.char array ? 248 | const item_info = @typeInfo(info.Pointer.child); 249 | if (item_info == .Array) { 250 | if (item_info.Array.child == c.char and (has_precision or has_sentinel_0(arg_type))) return; 251 | } 252 | }, 253 | .Many => { 254 | // pointer to c.char ? 255 | if (info.Pointer.child == c.char and (has_precision or has_sentinel_0(arg_type))) return; 256 | }, 257 | else => {}, 258 | }; 259 | }, 260 | c_pointer => { 261 | const info = @typeInfo(arg_type); 262 | if (info == .Pointer) return; 263 | 264 | // sizeof(optional-pointer) == sizeof(pointer) 265 | // `null` of the optional-pointer is guaranteed to be address `0` 266 | if (info == .Optional) { 267 | const UnwrappedType = info.Optional.child; 268 | if (@typeInfo(UnwrappedType) == .Pointer) return; 269 | } 270 | }, 271 | else => { 272 | if (arg_type == expect_type) return; 273 | }, 274 | } 275 | 276 | @compileError("expect a " ++ @typeName(expect_type) ++ " for specifier %" ++ modifier.desc() ++ [_]u8{specifier} ++ ", but got a " ++ @typeName(arg_type)); 277 | } 278 | 279 | fn has_sentinel_0(comptime T: type) bool { 280 | const end = std.meta.sentinel(T) orelse return false; 281 | return end == 0; 282 | } 283 | 284 | // ================================================================================ 285 | 286 | fn do_check(comptime in_format: [:0]const u8, comptime ArgsType: type) void { 287 | var format = FormatIterator.init(in_format); 288 | 289 | const typeinfo: Type = @typeInfo(ArgsType); 290 | 291 | if (typeinfo != .Struct) 292 | @compileError("args must be a tuple, got " ++ @typeName(ArgsType)); 293 | 294 | const info = typeinfo.Struct; 295 | if (!info.is_tuple) 296 | @compileError("args must be a tuple, got " ++ @typeName(ArgsType)); 297 | 298 | var args = ArgsIterator.init(info.fields); 299 | 300 | // %[flags][width][.precision][modifier]specifier 301 | while (format.pop()) |char| { 302 | if (char != '%') continue; 303 | 304 | // %% means % character 305 | const next_char = format.peek() orelse @compileError("expect a specifier character"); 306 | if (next_char == '%') { 307 | format.pop_force_void(); 308 | continue; 309 | } 310 | 311 | parse_flags(&format); 312 | parse_width(&format, &args); 313 | const has_precision = parse_precision(&format, &args); 314 | const modifier = parse_modifier(&format); 315 | parse_specifier(&format, &args, modifier, has_precision); 316 | } 317 | 318 | if (!args.empty()) { 319 | const pos = args.pos + 1; 320 | @compileError(std.fmt.comptimePrint("there is a redundant argument {s} at position {}", .{ @typeName(args.pop_force().field_type), pos })); 321 | } 322 | } 323 | 324 | /// check whether printf's format string and parameter type match each other (comptime) 325 | pub fn check(comptime format: [:0]const u8, args: anytype) void { 326 | comptime do_check(format, @TypeOf(args)); 327 | } 328 | 329 | pub fn @"test: checker"() !void { 330 | // integer 331 | var schar: c.schar = 10; 332 | var uchar: c.uchar = 10; 333 | var short: c_short = 10; 334 | var ushort: c_ushort = 10; 335 | var int: c_int = 10; 336 | var uint: c_uint = 10; 337 | var long: c_long = 10; 338 | var ulong: c_ulong = 10; 339 | var longlong: c_longlong = 10; 340 | var ulonglong: c_ulonglong = 10; 341 | var intmax: c.intmax_t = 10; 342 | var uintmax: c.uintmax_t = 10; 343 | var ssize: isize = 10; // signed 344 | var size: usize = 10; // unsigned 345 | var ptrdiff: c.ptrdiff_t = 10; // signed 346 | 347 | // floating-point 348 | var float: c.float = 10.32; 349 | var double: c.double = 3.14159; 350 | var longdouble: c_longdouble = 123.456; 351 | 352 | // character 353 | var char: c.char = 10; 354 | 355 | // string 356 | var string_p_array = "world"; // string literal: *const [N:0]u8 357 | var string_p_many: [*:0]const c.char = string_p_array; 358 | 359 | // pointer 360 | var pointer_optional: ?*const anyopaque = null; 361 | var pointer = &ptrdiff; 362 | 363 | // ======================================================== 364 | 365 | check("hello, world\n", .{}); 366 | 367 | // signed integer %d %i 368 | check("hello, %s %hhd %s\n", .{ "world", schar, string_p_many }); 369 | check("hello, %s %hd %s\n", .{ "world", short, string_p_array }); 370 | check("hello, %s %d %s\n", .{ "world", int, string_p_array }); 371 | check("hello, %s %ld %s\n", .{ "world", long, string_p_many }); 372 | check("hello, %s %lli %s\n", .{ "world", longlong, string_p_many }); 373 | check("hello, %s %ji %s\n", .{ "world", intmax, string_p_array }); 374 | check("hello, %s %zi %s\n", .{ "world", ssize, string_p_many }); 375 | check("hello, %s %ti %s\n", .{ "world", ptrdiff, string_p_array }); 376 | 377 | // unsigned integer %u %o %x %X 378 | check("hello, %s %hhu %s\n", .{ "world", uchar, string_p_many }); 379 | check("hello, %s %hu %s\n", .{ "world", ushort, string_p_array }); 380 | check("hello, %s %o %s\n", .{ "world", uint, string_p_array }); 381 | check("hello, %s %lo %s\n", .{ "world", ulong, string_p_many }); 382 | check("hello, %s %llx %s\n", .{ "world", ulonglong, string_p_array }); 383 | check("hello, %s %jx %s\n", .{ "world", uintmax, string_p_many }); 384 | check("hello, %s %zX %s\n", .{ "world", size, string_p_many }); 385 | check("hello, %s %tX %s\n", .{ "world", ptrdiff, string_p_array }); 386 | 387 | // float/double %f %F %e %E %g %G %a %A 388 | check("hello, %s %f %s\n", .{ "world", float, string_p_many }); 389 | check("hello, %s %F %s\n", .{ "world", double, string_p_array }); 390 | check("hello, %s %e %s\n", .{ "world", float, string_p_many }); 391 | check("hello, %s %E %s\n", .{ "world", double, string_p_array }); 392 | check("hello, %s %g %s\n", .{ "world", float, string_p_many }); 393 | check("hello, %s %G %s\n", .{ "world", double, string_p_many }); 394 | check("hello, %s %a %s\n", .{ "world", float, string_p_array }); 395 | check("hello, %s %A %s\n", .{ "world", double, string_p_array }); 396 | 397 | // long double %f %F %e %E %g %G %a %A 398 | check("hello, %s %Lf %s\n", .{ "world", longdouble, string_p_many }); 399 | check("hello, %s %LF %s\n", .{ "world", longdouble, string_p_many }); 400 | check("hello, %s %Le %s\n", .{ "world", longdouble, string_p_array }); 401 | check("hello, %s %LE %s\n", .{ "world", longdouble, string_p_array }); 402 | check("hello, %s %Lg %s\n", .{ "world", longdouble, string_p_array }); 403 | check("hello, %s %LG %s\n", .{ "world", longdouble, string_p_many }); 404 | check("hello, %s %La %s\n", .{ "world", longdouble, string_p_many }); 405 | check("hello, %s %LA %s\n", .{ "world", longdouble, string_p_many }); 406 | 407 | // character %c 408 | check("hello, %s %LA %s %c\n", .{ "world", longdouble, string_p_many, char }); 409 | 410 | // string %s 411 | check("hello, %s %s %%\n", .{ "world", "foo" }); 412 | check("hello, %s %s %%%%\n", .{ "world", string_p_array }); 413 | check("hello, %s %s %%\n", .{ "world", string_p_many }); 414 | check("hello, %s %s %%%%\n", .{ "world", string_p_array }); 415 | check("hello, %s %s %%\n", .{ "world", string_p_many }); 416 | 417 | // pointer %p 418 | check("hello, %s %s %% %p\n", .{ "world", string_p_array, pointer_optional }); 419 | check("hello, %s %s %% %p\n", .{ "world", string_p_many, &pointer_optional }); 420 | check("hello, %s %s %% %p\n", .{ "world", string_p_array, pointer }); 421 | check("hello, %s %s %% %p\n", .{ "world", string_p_many, &pointer }); 422 | 423 | // get the number of characters written %n 424 | check("hello, %s %hhn %s\n", .{ "world", &schar, string_p_array }); 425 | check("hello, %s %hn %s\n", .{ "world", &short, string_p_array }); 426 | check("hello, %s %n %s\n", .{ "world", &int, string_p_many }); 427 | check("hello, %s %ln %s\n", .{ "world", &long, string_p_many }); 428 | check("hello, %s %lln %s\n", .{ "world", &longlong, string_p_array }); 429 | check("hello, %s %jn %s\n", .{ "world", &intmax, string_p_array }); 430 | check("hello, %s %zn %s\n", .{ "world", &ssize, string_p_many }); 431 | check("hello, %s %tn %s\n", .{ "world", &ptrdiff, string_p_many }); 432 | 433 | // strerror(errno) %m 434 | check("failed to do something: (%d) %m\n", .{cc.errno()}); // supported by glibc and musl 435 | check("failed to do something: (%#m) %m\n", .{}); // supported by glibc only. in musl, %#m equals %m 436 | 437 | // flags 438 | // '#': alternate form 439 | // '-': left justified 440 | // '0': zero padded (for integer and floating-point) 441 | // '+': a sign (+ or -) should always be placed before a number produced by a signed conversion 442 | // ' ': if the result of a signed conversion does not start with a sign character, or is empty, space is prepended to the result. 443 | check("hello %% %+d % ld %+lli % hhd %%", .{ int, long, longlong, schar }); 444 | check("hello %% %#o %#lx %#llX %%", .{ uint, ulong, ulonglong }); 445 | check("hello %% %#-o %#lx %#-llX %%", .{ uint, ulong, ulonglong }); 446 | check("hello %% %#f %#F %#e %#E %#g %#G %#a %#A %%", .{ float, double, float, double, float, double, float, double }); 447 | check("hello %% %#Lf %#LF %#Le %#LE %%", .{ longdouble, longdouble, longdouble, longdouble }); 448 | check("hello %% %#Lg %#LG %#La %#LA %%", .{ longdouble, longdouble, longdouble, longdouble }); 449 | 450 | // width 451 | check("hello, %ld %123s %lld\n", .{ long, string_p_many, longlong }); 452 | check("hello, %ld %*s %lld\n", .{ long, int, string_p_array, longlong }); 453 | 454 | // precision 455 | check("hello, %lld %.12s %lld\n", .{ longlong, string_p_array, longlong }); 456 | check("hello, %hhd %.*s %hu\n", .{ schar, int, string_p_array, ushort }); 457 | check("hello, %hhd %.s %hu\n", .{ schar, string_p_many, ushort }); 458 | 459 | // width + precision 460 | check("hello, %ld %20.33s %lld\n", .{ long, string_p_many, longlong }); 461 | check("hello, %ld %20.*s %lld\n", .{ long, int, string_p_array, longlong }); 462 | check("hello, %ld %20.s %lld\n", .{ long, string_p_many, longlong }); 463 | check("hello, %ld %*.20s %lld\n", .{ long, int, string_p_array, longlong }); 464 | check("hello, %ld %*.*s %lld\n", .{ long, int, int, string_p_array, longlong }); 465 | check("hello, %ld %*.s %lld\n", .{ long, int, string_p_many, longlong }); 466 | } 467 | -------------------------------------------------------------------------------- /src/g.zig: -------------------------------------------------------------------------------- 1 | //! global variables 2 | 3 | const std = @import("std"); 4 | const DynStr = @import("DynStr.zig"); 5 | const StrList = @import("StrList.zig"); 6 | const EvLoop = @import("EvLoop.zig"); 7 | const Config = @import("Config.zig"); 8 | const in = @import("in.zig"); 9 | const out = @import("out.zig"); 10 | const flags_op = @import("flags_op.zig"); 11 | 12 | pub var evloop: EvLoop = undefined; 13 | 14 | /// global memory allocator 15 | pub var allocator: std.mem.Allocator = undefined; 16 | 17 | pub var config: Config = undefined; 18 | pub var in_config: in.Config = undefined; 19 | pub var out_config: out.Config = undefined; 20 | 21 | pub const Flags = enum(u8) { 22 | verbose = 1 << 0, 23 | _, 24 | pub usingnamespace flags_op.get(Flags); 25 | }; 26 | 27 | pub var flags: Flags = Flags.empty(); 28 | 29 | pub inline fn verbose() bool { 30 | return flags.has(.verbose); 31 | } 32 | 33 | pub var cert_verify: bool = false; 34 | 35 | /// the location of CA certs 36 | pub var ca_certs: DynStr = .{}; 37 | -------------------------------------------------------------------------------- /src/in.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const build_opts = @import("build_opts"); 3 | const cfg = @import("cfg.zig"); 4 | const log = @import("log.zig"); 5 | const cc = @import("cc.zig"); 6 | const g = @import("g.zig"); 7 | 8 | const modules = b: { 9 | const s = struct { 10 | const all_modules = .{ 11 | @import("in_tproxy.zig"), 12 | @import("in_socks.zig"), 13 | @import("in_tlsproxy.zig"), 14 | @import("in_trojan.zig"), 15 | }; 16 | fn is_enable(comptime mod: type) bool { 17 | return @field(build_opts, "in_" ++ mod.NAME); 18 | } 19 | fn len() comptime_int { 20 | var n = 0; 21 | for (all_modules) |mod| { 22 | if (is_enable(mod)) 23 | n += 1; 24 | } 25 | return n; 26 | } 27 | fn fill(comptime list: []type) void { 28 | var n = 0; 29 | for (all_modules) |mod| { 30 | if (is_enable(mod)) { 31 | list[n] = mod; 32 | n += 1; 33 | } 34 | } 35 | } 36 | }; 37 | var array: [s.len()]type = undefined; 38 | s.fill(&array); 39 | break :b array; 40 | }; 41 | 42 | pub const Config = b: { 43 | var enum_fields: [modules.len]std.builtin.Type.EnumField = undefined; 44 | var union_fields: [modules.len]std.builtin.Type.UnionField = undefined; 45 | var n = 0; 46 | for (modules) |mod| { 47 | enum_fields[n] = .{ 48 | .name = mod.NAME, 49 | .value = n, 50 | }; 51 | union_fields[n] = .{ 52 | .name = mod.NAME, 53 | .field_type = mod.Config, 54 | .alignment = @alignOf(mod.Config), 55 | }; 56 | n += 1; 57 | } 58 | break :b @Type(.{ .Union = .{ 59 | .layout = .Auto, 60 | .fields = union_fields[0..n], 61 | .decls = &.{}, 62 | .tag_type = @Type(.{ .Enum = .{ 63 | .layout = .Auto, 64 | .tag_type = u8, 65 | .fields = enum_fields[0..n], 66 | .decls = &.{}, 67 | .is_exhaustive = true, 68 | } }), 69 | } }); 70 | }; 71 | 72 | pub fn load_config(proto_name: []const u8, content: []const u8) ?Config { 73 | inline for (modules) |mod| { 74 | if (std.mem.eql(u8, proto_name, mod.NAME)) { 75 | return @unionInit( 76 | Config, 77 | mod.NAME, 78 | mod.Config.load(content) orelse return null, 79 | ); 80 | } 81 | } 82 | log.err(@src(), "unknown proto: %.*s", .{ cc.to_int(proto_name.len), proto_name.ptr }); 83 | return null; 84 | } 85 | -------------------------------------------------------------------------------- /src/in_socks.zig: -------------------------------------------------------------------------------- 1 | const cfg_loader = @import("cfg_loader.zig"); 2 | const cfg_checker = @import("cfg_checker.zig"); 3 | 4 | pub const NAME = "socks"; 5 | 6 | pub const Config = struct { 7 | passwd: []const [:0]const u8 = &.{}, // "user passwd", "user passwd" 8 | ip: []const [:0]const u8 = &.{}, 9 | port: u16 = 1080, 10 | tcp: bool = true, 11 | udp: bool = true, 12 | 13 | pub fn load(content: []const u8) ?Config { 14 | var self = Config{}; 15 | const src = @src(); 16 | cfg_loader.load(src, &self, content) orelse return null; 17 | cfg_checker.check_ips(src, self.ip) orelse return null; 18 | cfg_checker.check_port(src, self.port) orelse return null; 19 | cfg_checker.check_tcp_udp(src, self.tcp, self.udp) orelse return null; 20 | self.set_default_value(); 21 | return self; 22 | } 23 | 24 | fn set_default_value(self: *Config) void { 25 | if (self.ip.len == 0) 26 | self.ip = opaque { 27 | const ips = &.{"0.0.0.0"}; 28 | }.ips; 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /src/in_tlsproxy.zig: -------------------------------------------------------------------------------- 1 | const cfg_loader = @import("cfg_loader.zig"); 2 | const cfg_checker = @import("cfg_checker.zig"); 3 | 4 | pub const NAME = "tlsproxy"; 5 | 6 | pub const Config = struct { 7 | ip: [:0]const u8 = "", 8 | port: u16 = 60080, 9 | tcp: bool = true, 10 | udp: bool = true, 11 | 12 | pub fn load(content: []const u8) ?Config { 13 | var self = Config{}; 14 | const src = @src(); 15 | cfg_loader.load(src, &self, content) orelse return null; 16 | cfg_checker.check_ip(src, self.ip) orelse return null; 17 | cfg_checker.check_port(src, self.port) orelse return null; 18 | cfg_checker.check_tcp_udp(src, self.tcp, self.udp) orelse return null; 19 | self.set_default_value(); 20 | return self; 21 | } 22 | 23 | fn set_default_value(self: *Config) void { 24 | if (self.ip.len == 0) 25 | self.ip = "127.0.0.1"; 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /src/in_tproxy.zig: -------------------------------------------------------------------------------- 1 | const cfg_loader = @import("cfg_loader.zig"); 2 | const cfg_checker = @import("cfg_checker.zig"); 3 | 4 | pub const NAME = "tproxy"; 5 | 6 | pub const Config = struct { 7 | ip: []const [:0]const u8 = &.{}, 8 | port: u16 = 60080, 9 | tcp: bool = true, 10 | udp: bool = true, 11 | 12 | pub fn load(content: []const u8) ?Config { 13 | var self = Config{}; 14 | const src = @src(); 15 | cfg_loader.load(src, &self, content) orelse return null; 16 | cfg_checker.check_ips(src, self.ip) orelse return null; 17 | cfg_checker.check_port(src, self.port) orelse return null; 18 | cfg_checker.check_tcp_udp(src, self.tcp, self.udp) orelse return null; 19 | self.set_default_value(); 20 | return self; 21 | } 22 | 23 | fn set_default_value(self: *Config) void { 24 | if (self.ip.len == 0) { 25 | self.ip = opaque { 26 | const ips = &.{"127.0.0.1"}; 27 | }.ips; 28 | } 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /src/in_trojan.zig: -------------------------------------------------------------------------------- 1 | const cfg_loader = @import("cfg_loader.zig"); 2 | const cfg_checker = @import("cfg_checker.zig"); 3 | 4 | pub const NAME = "trojan"; 5 | 6 | pub const Config = struct { 7 | passwd: []const [:0]const u8 = &.{}, 8 | ip: []const [:0]const u8 = &.{}, 9 | port: u16 = 443, 10 | tcp: bool = true, 11 | udp: bool = true, 12 | 13 | pub fn load(content: []const u8) ?Config { 14 | var self = Config{}; 15 | const src = @src(); 16 | cfg_loader.load(src, &self, content) orelse return null; 17 | cfg_checker.required(src, self.passwd.len > 0, "passwd") orelse return null; 18 | cfg_checker.check_ips(src, self.ip) orelse return null; 19 | cfg_checker.check_port(src, self.port) orelse return null; 20 | cfg_checker.check_tcp_udp(src, self.tcp, self.udp) orelse return null; 21 | self.set_default_value(); 22 | return self; 23 | } 24 | 25 | fn set_default_value(self: *Config) void { 26 | if (self.ip.len == 0) 27 | self.ip = opaque { 28 | const ips = &.{"0.0.0.0"}; 29 | }.ips; 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /src/log.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include "log.h" 3 | 4 | const struct tm *get_tm(void) { 5 | return localtime(&(time_t){time(NULL)}); 6 | } 7 | -------------------------------------------------------------------------------- /src/log.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #ifndef LOG_FILENAME 7 | #define LOG_FILENAME __FILE__ 8 | #endif 9 | 10 | const struct tm *get_tm(void); 11 | 12 | #define log_write(color, level, fmt, args...) ({ \ 13 | const struct tm *tm_ = get_tm(); \ 14 | printf("\e[" color ";1m%d-%02d-%02d %02d:%02d:%02d " level "\e[0m " \ 15 | "\e[1m[%s:%d %s]\e[0m " fmt "\n", \ 16 | tm_->tm_year + 1900, tm_->tm_mon + 1, tm_->tm_mday, \ 17 | tm_->tm_hour, tm_->tm_min, tm_->tm_sec, \ 18 | LOG_FILENAME, __LINE__, __func__, ##args); \ 19 | }) 20 | 21 | #define log_info(fmt, args...) \ 22 | log_write("32", "I", fmt, ##args) 23 | 24 | #define log_warning(fmt, args...) \ 25 | log_write("33", "W", fmt, ##args) 26 | 27 | #define log_error(fmt, args...) \ 28 | log_write("35", "E", fmt, ##args) 29 | -------------------------------------------------------------------------------- /src/log.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const cc = @import("cc.zig"); 3 | const SourceLocation = std.builtin.SourceLocation; 4 | 5 | const Level = enum { 6 | Debug, 7 | Info, 8 | Warning, 9 | Error, 10 | 11 | fn desc(level: Level) [:0]const u8 { 12 | return switch (level) { 13 | .Debug => "D", 14 | .Info => "I", 15 | .Warning => "W", 16 | .Error => "E", 17 | }; 18 | } 19 | 20 | fn color(level: Level) [:0]const u8 { 21 | return switch (level) { 22 | .Debug => "34", 23 | .Info => "32", 24 | .Warning => "33", 25 | .Error => "35", 26 | }; 27 | } 28 | }; 29 | 30 | /// year, month, day, hour, min, sec 31 | noinline fn time() [6]c_int { 32 | const tm = cc.localtime(cc.time()).?; 33 | return .{ tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec }; 34 | } 35 | 36 | pub fn srcinfo(comptime src: SourceLocation) [:0]const u8 { 37 | const filename = b: { 38 | // remove directories from path 39 | // can't use std.mem.lastIndexOfScalar because of compiler bugs 40 | var i = src.file.len - 1; 41 | while (i >= 0) : (i -= 1) 42 | if (src.file[i] == '/') break; 43 | break :b src.file[i + 1 ..]; 44 | }; 45 | const fn_name = b: { 46 | // remove top-level namespace (filename) 47 | const i = std.mem.indexOfScalar(u8, src.fn_name, '.') orelse -1; 48 | break :b src.fn_name[i + 1 ..]; 49 | }; 50 | return std.fmt.comptimePrint("[{s}:{d} {s}]", .{ filename, src.line, fn_name }); 51 | } 52 | 53 | fn log_write(comptime level: Level, comptime src: SourceLocation, comptime in_fmt: [:0]const u8, in_args: anytype) void { 54 | const timefmt = "%d-%02d-%02d %02d:%02d:%02d"; 55 | const fmt = "\x1b[" ++ level.color() ++ ";1m" ++ timefmt ++ " " ++ level.desc() ++ "\x1b[0m \x1b[1m%s\x1b[0m" ++ " " ++ in_fmt ++ "\n"; 56 | const t = time(); 57 | const args = .{ t[0], t[1], t[2], t[3], t[4], t[5], comptime srcinfo(src).ptr } ++ in_args; 58 | @call(.{}, cc.printf, .{ fmt, args }); 59 | } 60 | 61 | pub fn debug(comptime src: SourceLocation, comptime fmt: [:0]const u8, args: anytype) void { 62 | return log_write(.Debug, src, fmt, args); 63 | } 64 | 65 | pub fn info(comptime src: SourceLocation, comptime fmt: [:0]const u8, args: anytype) void { 66 | return log_write(.Info, src, fmt, args); 67 | } 68 | 69 | pub fn warn(comptime src: SourceLocation, comptime fmt: [:0]const u8, args: anytype) void { 70 | return log_write(.Warning, src, fmt, args); 71 | } 72 | 73 | pub fn err(comptime src: SourceLocation, comptime fmt: [:0]const u8, args: anytype) void { 74 | return log_write(.Error, src, fmt, args); 75 | } 76 | -------------------------------------------------------------------------------- /src/main.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const builtin = @import("builtin"); 3 | const build_opts = @import("build_opts"); 4 | const modules = @import("modules.zig"); 5 | const tests = @import("tests.zig"); 6 | const c = @import("c.zig"); 7 | const cc = @import("cc.zig"); 8 | const g = @import("g.zig"); 9 | const log = @import("log.zig"); 10 | const opt = @import("opt.zig"); 11 | const net = @import("net.zig"); 12 | const server = @import("server.zig"); 13 | const EvLoop = @import("EvLoop.zig"); 14 | const co = @import("co.zig"); 15 | 16 | // ============================================================================ 17 | 18 | /// the rewrite is to avoid generating unnecessary code in release mode. 19 | pub fn panic(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, ret_addr: ?usize) noreturn { 20 | @setCold(true); 21 | if (builtin.mode == .Debug or builtin.mode == .ReleaseSafe) 22 | std.builtin.default_panic(msg, error_return_trace, ret_addr) 23 | else 24 | cc.abort(); 25 | } 26 | 27 | // ============================================================================ 28 | 29 | const _debug = builtin.mode == .Debug; 30 | 31 | const gpa_t = if (_debug) std.heap.GeneralPurposeAllocator(.{}) else void; 32 | var _gpa: gpa_t = undefined; 33 | 34 | const pipe_fds_t = if (_debug) [2]c_int else void; 35 | var _pipe_fds: pipe_fds_t = undefined; 36 | 37 | fn on_sigusr1(_: c_int) callconv(.C) void { 38 | const v: u8 = 0; 39 | _ = cc.write(_pipe_fds[1], std.mem.asBytes(&v)); 40 | } 41 | 42 | fn memleak_checker() void { 43 | defer co.terminate(@frame(), @frameSize(memleak_checker)); 44 | 45 | cc.pipe2(&_pipe_fds, c.O_CLOEXEC | c.O_NONBLOCK) orelse { 46 | log.err(@src(), "pipe() failed: (%d) %m", .{cc.errno()}); 47 | @panic("pipe failed"); 48 | }; 49 | defer _ = cc.close(_pipe_fds[1]); // write end 50 | 51 | // register sig_handler 52 | _ = cc.signal(c.SIGUSR1, on_sigusr1); 53 | 54 | const fdobj = EvLoop.Fd.new(_pipe_fds[0]); 55 | defer fdobj.free(); // read end 56 | 57 | while (true) { 58 | var v: u8 = undefined; 59 | _ = g.evloop.read(fdobj, std.mem.asBytes(&v)) orelse { 60 | log.err(@src(), "read(%d) failed: (%d) %m", .{ fdobj.fd, cc.errno() }); 61 | continue; 62 | }; 63 | log.info(@src(), "signal received, check memory leaks ...", .{}); 64 | _ = _gpa.detectLeaks(); 65 | } 66 | } 67 | 68 | // ============================================================================ 69 | 70 | /// called by EvLoop.check_timeout 71 | // pub const check_timeout = server.check_timeout; 72 | pub fn check_timeout() c_int { 73 | // TODO 74 | return -1; 75 | } 76 | 77 | pub fn main() u8 { 78 | g.allocator = if (_debug) b: { 79 | _gpa = gpa_t{}; 80 | break :b _gpa.allocator(); 81 | } else std.heap.c_allocator; 82 | 83 | defer { 84 | if (_debug) 85 | _ = _gpa.deinit(); 86 | } 87 | 88 | // ============================================================================ 89 | 90 | _ = cc.signal(c.SIGPIPE, cc.SIG_IGN()); 91 | 92 | _ = cc.setvbuf(cc.stdout, null, c._IOLBF, 256); 93 | 94 | // setting default values for TZ 95 | _ = cc.setenv("TZ", ":/etc/localtime", false); 96 | 97 | // ============================================================================ 98 | 99 | // used only for business-independent initialization, such as global variables 100 | init_all_module(); 101 | defer if (_debug) deinit_all_module(); 102 | 103 | // ============================================================================ 104 | 105 | if (build_opts.is_test) 106 | return tests.main(); 107 | 108 | // ============================================================================ 109 | 110 | opt.parse(); 111 | 112 | net.init(); 113 | 114 | const src = @src(); 115 | _ = src; // autofix 116 | 117 | // ============================================================================ 118 | 119 | // TODO: multi threads 120 | g.evloop = EvLoop.init(); 121 | 122 | server.start(); 123 | 124 | if (_debug) 125 | co.create(memleak_checker, .{}); 126 | 127 | g.evloop.run(); 128 | 129 | return 0; 130 | } 131 | 132 | fn init_all_module() void { 133 | comptime var i = 0; 134 | inline while (i < modules.module_list.len) : (i += 1) { 135 | const module = modules.module_list[i]; 136 | const module_name: cc.ConstStr = modules.name_list[i]; 137 | if (@hasDecl(module, "module_init")) { 138 | if (false) log.debug(@src(), "%s.module_init()", .{module_name}); 139 | module.module_init(); 140 | } 141 | } 142 | } 143 | 144 | fn deinit_all_module() void { 145 | comptime var i = 0; 146 | inline while (i < modules.module_list.len) : (i += 1) { 147 | const module = modules.module_list[i]; 148 | const module_name: cc.ConstStr = modules.name_list[i]; 149 | if (@hasDecl(module, "module_deinit")) { 150 | if (false) log.debug(@src(), "%s.module_deinit()", .{module_name}); 151 | module.module_deinit(); 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/misc.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include "misc.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | const void *SIG_IGNORE(void) { 9 | return SIG_IGN; 10 | } 11 | 12 | const void *SIG_DEFAULT(void) { 13 | return SIG_DFL; 14 | } 15 | 16 | const void *SIG_ERROR(void) { 17 | return SIG_ERR; 18 | } 19 | 20 | bool is_dir(const char *path) { 21 | struct stat st; 22 | if (stat(path, &st) == 0) 23 | return S_ISDIR(st.st_mode); 24 | return false; 25 | } 26 | 27 | ssize_t fstat_size(int fd) { 28 | struct stat st; 29 | if (fstat(fd, &st) == 0) 30 | return st.st_size; 31 | return -1; 32 | } 33 | 34 | bool has_aes(void) { 35 | bool found = false; 36 | 37 | FILE *f = fopen("/proc/cpuinfo", "r"); 38 | if (!f) goto out; 39 | 40 | char buf[10]; 41 | while (fscanf(f, "%9s", buf) > 0) { 42 | if (strstr(buf, "aes")) { 43 | found = true; 44 | break; 45 | } 46 | } 47 | 48 | out: 49 | if (f) fclose(f); 50 | return found; 51 | } 52 | -------------------------------------------------------------------------------- /src/misc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #define likely(x) __builtin_expect(!!(x), 1) 8 | #define unlikely(x) __builtin_expect(!!(x), 0) 9 | 10 | #define likely_if(x) if (likely(x)) 11 | #define unlikely_if(x) if (unlikely(x)) 12 | 13 | #define STATIC_ASSERT(expr) _Static_assert(expr, #expr) 14 | 15 | /* 16 | void f(int n, int *noalias p, int *noalias q, const int *noalias a, const int *noalias b) { 17 | for (int i = 0; i < n; ++i) { 18 | p[i] = a[i] + b[i] 19 | q[i] = a[i] * b[i] 20 | } 21 | } 22 | p is not allowed to be aliased 23 | q is not allowed to be aliased 24 | a and b can be aliases for each other 25 | ================================================== 26 | strict aliasing: an object can only be aliased by "compatible type" or "char type" 27 | */ 28 | #define noalias restrict 29 | 30 | /* ======================================================== */ 31 | 32 | typedef uint8_t u8; 33 | typedef uint16_t u16; 34 | typedef uint32_t u32; 35 | typedef uint64_t u64; 36 | 37 | typedef int8_t s8; 38 | typedef int16_t s16; 39 | typedef int32_t s32; 40 | typedef int64_t s64; 41 | 42 | #define U8C UINT8_C 43 | #define U16C UINT16_C 44 | #define U32C UINT32_C 45 | #define U64C UINT64_C 46 | 47 | #define S8C INT8_C 48 | #define S16C INT16_C 49 | #define S32C INT32_C 50 | #define S64C INT64_C 51 | 52 | // typedef signed char byte; /* >= 8 bits */ 53 | typedef unsigned char ubyte; /* >= 8 bits */ 54 | typedef unsigned short ushort; /* >= 16 bits */ 55 | typedef unsigned int uint; /* >= 16 bits */ 56 | typedef unsigned long ulong; /* >= 32 bits */ 57 | typedef long long llong; /* >= 64 bits */ 58 | typedef unsigned long long ullong; /* >= 64 bits */ 59 | 60 | /* ======================================================== */ 61 | 62 | /* token stringize */ 63 | #define _literal(x) #x 64 | #define literal(x) _literal(x) 65 | 66 | /* to avoid breaking the constant properties of input parameters, do not use __auto_type or __typeof__ */ 67 | #define max(a, b) ((a) > (b) ? (a) : (b)) 68 | #define min(a, b) ((a) < (b) ? (a) : (b)) 69 | 70 | /* unsigned-integer variant: ceil(a / b) */ 71 | #define ceilu(a, b) (((ullong)(a) + (ullong)(b) - 1) / (ullong)(b)) 72 | 73 | /* number of elements */ 74 | #define array_n(a) (sizeof(a) / sizeof(*(a))) 75 | 76 | #define cast(t, v) ((t)(v)) 77 | 78 | /* ======================================================== */ 79 | 80 | /* zig is currently unable to translate the SIG_IGN/DFL/ERR macro */ 81 | const void *SIG_IGNORE(void); 82 | const void *SIG_DEFAULT(void); 83 | const void *SIG_ERROR(void); 84 | 85 | bool is_dir(const char *path); 86 | 87 | ssize_t fstat_size(int fd); 88 | 89 | bool has_aes(void); 90 | -------------------------------------------------------------------------------- /src/modules.zig: -------------------------------------------------------------------------------- 1 | pub const name_list = .{ "Config", "DynStr", "EvLoop", "ListNode", "Rc", "StrList", "c", "cc", "cfg", "cfg_checker", "cfg_loader", "co", "flags_op", "fmtchk", "g", "in", "in_socks", "in_tlsproxy", "in_tproxy", "in_trojan", "log", "main", "modules", "net", "opt", "out", "out_raw", "out_socks", "out_tlsproxy", "out_trojan", "sentinel_vector", "server", "str2int", "tests" }; 2 | pub const module_list = .{ Config, DynStr, EvLoop, ListNode, Rc, StrList, c, cc, cfg, cfg_checker, cfg_loader, co, flags_op, fmtchk, g, in, in_socks, in_tlsproxy, in_tproxy, in_trojan, log, main, modules, net, opt, out, out_raw, out_socks, out_tlsproxy, out_trojan, sentinel_vector, server, str2int, tests }; 3 | 4 | const Config = @import("Config.zig"); 5 | const DynStr = @import("DynStr.zig"); 6 | const EvLoop = @import("EvLoop.zig"); 7 | const ListNode = @import("ListNode.zig"); 8 | const Rc = @import("Rc.zig"); 9 | const StrList = @import("StrList.zig"); 10 | const c = @import("c.zig"); 11 | const cc = @import("cc.zig"); 12 | const cfg = @import("cfg.zig"); 13 | const cfg_checker = @import("cfg_checker.zig"); 14 | const cfg_loader = @import("cfg_loader.zig"); 15 | const co = @import("co.zig"); 16 | const flags_op = @import("flags_op.zig"); 17 | const fmtchk = @import("fmtchk.zig"); 18 | const g = @import("g.zig"); 19 | const in = @import("in.zig"); 20 | const in_socks = @import("in_socks.zig"); 21 | const in_tlsproxy = @import("in_tlsproxy.zig"); 22 | const in_tproxy = @import("in_tproxy.zig"); 23 | const in_trojan = @import("in_trojan.zig"); 24 | const log = @import("log.zig"); 25 | const main = @import("main.zig"); 26 | const modules = @import("modules.zig"); 27 | const net = @import("net.zig"); 28 | const opt = @import("opt.zig"); 29 | const out = @import("out.zig"); 30 | const out_raw = @import("out_raw.zig"); 31 | const out_socks = @import("out_socks.zig"); 32 | const out_tlsproxy = @import("out_tlsproxy.zig"); 33 | const out_trojan = @import("out_trojan.zig"); 34 | const sentinel_vector = @import("sentinel_vector.zig"); 35 | const server = @import("server.zig"); 36 | const str2int = @import("str2int.zig"); 37 | const tests = @import("tests.zig"); 38 | -------------------------------------------------------------------------------- /src/net.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include "net.h" 3 | #include "log.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | int (*RECVMMSG)(int sockfd, MMSGHDR *msgvec, unsigned int vlen, int flags, struct timespec *timeout); 10 | 11 | int (*SENDMMSG)(int sockfd, MMSGHDR *msgvec, unsigned int vlen, int flags); 12 | 13 | #ifdef MUSL 14 | static int syscall_recvmmsg(int sockfd, MMSGHDR *msgvec, unsigned int vlen, int flags, struct timespec *timeout) { 15 | return syscall(SYS_recvmmsg, sockfd, msgvec, vlen, flags, timeout); 16 | } 17 | #else 18 | #define syscall_recvmmsg recvmmsg 19 | #endif 20 | 21 | static int userspace_recvmmsg(int sockfd, MMSGHDR *msgvec, unsigned int vlen, int flags, struct timespec *timeout) { 22 | unlikely_if (vlen <= 0 || timeout) { 23 | errno = EINVAL; 24 | return -1; 25 | } 26 | 27 | bool wait_for_one = flags & MSG_WAITFORONE; 28 | flags &= ~MSG_WAITFORONE; 29 | 30 | int nrecv = 0; 31 | 32 | for (uint i = 0; i < vlen; ++i) { 33 | ssize_t res = RECVMSG(sockfd, &msgvec[i].msg_hdr, flags); 34 | if (res < 0) break; 35 | 36 | msgvec[i].msg_len = res; 37 | ++nrecv; 38 | 39 | if (wait_for_one) 40 | flags |= MSG_DONTWAIT; 41 | } 42 | 43 | return nrecv ?: -1; 44 | } 45 | 46 | #ifdef MUSL 47 | static int syscall_sendmmsg(int sockfd, MMSGHDR *msgvec, unsigned int vlen, int flags) { 48 | return syscall(SYS_sendmmsg, sockfd, msgvec, vlen, flags); 49 | } 50 | #else 51 | #define syscall_sendmmsg sendmmsg 52 | #endif 53 | 54 | static int userspace_sendmmsg(int sockfd, MMSGHDR *msgvec, unsigned int vlen, int flags) { 55 | unlikely_if (vlen <= 0) { 56 | errno = EINVAL; 57 | return -1; 58 | } 59 | 60 | int nsent = 0; 61 | 62 | for (uint i = 0; i < vlen; ++i) { 63 | ssize_t res = SENDMSG(sockfd, &msgvec[i].msg_hdr, flags); 64 | if (res < 0) break; 65 | 66 | msgvec[i].msg_len = res; 67 | ++nsent; 68 | } 69 | 70 | return nsent ?: -1; 71 | } 72 | 73 | void net_init(void) { 74 | int res = syscall_recvmmsg(-1, NULL, 0, 0, NULL); 75 | assert(res == -1); 76 | (void)res; 77 | 78 | if (errno != ENOSYS) { 79 | RECVMMSG = (__typeof__(RECVMMSG))syscall_recvmmsg; 80 | } else { 81 | log_info("recvmmsg not implemented, use recvmsg to simulate"); 82 | RECVMMSG = userspace_recvmmsg; 83 | } 84 | 85 | res = syscall_sendmmsg(-1, NULL, 0, 0); 86 | assert(res == -1); 87 | (void)res; 88 | 89 | if (errno != ENOSYS) { 90 | SENDMMSG = (__typeof__(SENDMMSG))syscall_sendmmsg; 91 | } else { 92 | log_info("sendmmsg not implemented, use sendmsg to simulate"); 93 | SENDMMSG = userspace_sendmmsg; 94 | } 95 | } 96 | 97 | u32 epev_get_events(const void *noalias ev) { 98 | return cast(const struct epoll_event *, ev)->events; 99 | } 100 | 101 | void *epev_get_ptrdata(const void *noalias ev) { 102 | return cast(const struct epoll_event *, ev)->data.ptr; 103 | } 104 | 105 | void epev_set_events(void *noalias ev, u32 events) { 106 | cast(struct epoll_event *, ev)->events = events; 107 | } 108 | 109 | void epev_set_ptrdata(void *noalias ev, const void *ptrdata) { 110 | cast(struct epoll_event *, ev)->data.ptr = (void *)ptrdata; 111 | } 112 | -------------------------------------------------------------------------------- /src/net.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "misc.h" 4 | #include 5 | #include 6 | #include 7 | 8 | /* ipv4/ipv6 address length (binary) */ 9 | #define IPV4_LEN 4 /* 4byte, 32bit */ 10 | #define IPV6_LEN 16 /* 16byte, 128bit */ 11 | 12 | /* https://git.musl-libc.org/cgit/musl/tree/src/network/sendmmsg.c */ 13 | /* https://man7.org/linux/man-pages/man2/sendmsg.2.html */ 14 | #ifdef MUSL 15 | typedef struct MSGHDR { 16 | void *msg_name; /* Optional address */ 17 | socklen_t msg_namelen; /* Size of address */ 18 | struct iovec *msg_iov; /* Scatter/gather array */ 19 | size_t msg_iovlen; /* # elements in msg_iov */ 20 | void *msg_control; /* Ancillary data, see below */ 21 | size_t msg_controllen; /* Ancillary data buffer len */ 22 | int msg_flags; /* Flags (unused) */ 23 | } MSGHDR; 24 | typedef struct MMSGHDR { 25 | struct MSGHDR msg_hdr; /* Message header */ 26 | unsigned int msg_len; /* return value of recvmsg/sendmsg */ 27 | } MMSGHDR; 28 | #else 29 | typedef struct msghdr MSGHDR; 30 | typedef struct mmsghdr MMSGHDR; 31 | #endif 32 | 33 | #ifdef MUSL 34 | static inline ssize_t RECVMSG(int sockfd, MSGHDR *msg, int flags) { 35 | return syscall(SYS_recvmsg, sockfd, msg, flags); 36 | } 37 | static inline ssize_t SENDMSG(int sockfd, const MSGHDR *msg, int flags) { 38 | return syscall(SYS_sendmsg, sockfd, msg, flags); 39 | } 40 | #else 41 | #define RECVMSG recvmsg 42 | #define SENDMSG sendmsg 43 | #endif 44 | 45 | /* compatible with old kernel (runtime) */ 46 | extern int (*RECVMMSG)(int sockfd, MMSGHDR *msgvec, unsigned int vlen, int flags, struct timespec *timeout); 47 | 48 | /* compatible with old kernel (runtime) */ 49 | extern int (*SENDMMSG)(int sockfd, MMSGHDR *msgvec, unsigned int vlen, int flags); 50 | 51 | void net_init(void); 52 | 53 | u32 epev_get_events(const void *noalias ev); 54 | void *epev_get_ptrdata(const void *noalias ev); 55 | 56 | void epev_set_events(void *noalias ev, u32 events); 57 | void epev_set_ptrdata(void *noalias ev, const void *ptrdata); 58 | -------------------------------------------------------------------------------- /src/net.zig: -------------------------------------------------------------------------------- 1 | const g = @import("g.zig"); 2 | const c = @import("c.zig"); 3 | const cc = @import("cc.zig"); 4 | const log = @import("log.zig"); 5 | 6 | // =============================================================== 7 | 8 | pub inline fn init() void { 9 | return c.net_init(); 10 | } 11 | 12 | // =============================================================== 13 | 14 | pub const SockType = enum { 15 | tcp, 16 | udp, 17 | 18 | /// string literal 19 | pub fn str(self: SockType) cc.ConstStr { 20 | return switch (self) { 21 | .tcp => "tcp", 22 | .udp => "udp", 23 | }; 24 | } 25 | 26 | /// c.SOCK_STREAM, c.SOCK_DGRAM 27 | pub fn value(self: SockType) c_int { 28 | return switch (self) { 29 | .tcp => c.SOCK_STREAM, 30 | .udp => c.SOCK_DGRAM, 31 | }; 32 | } 33 | }; 34 | 35 | pub noinline fn new_sock(family: c.sa_family_t, socktype: SockType) ?c_int { 36 | return cc.socket(family, socktype.value() | c.SOCK_NONBLOCK | c.SOCK_CLOEXEC, 0) orelse { 37 | const str_family = if (family == c.AF_INET) "ipv4" else "ipv6"; 38 | log.err(@src(), "socket(%s, %s) failed: (%d) %m", .{ str_family, socktype.str(), cc.errno() }); 39 | return null; 40 | }; 41 | } 42 | 43 | pub fn new_listen_sock(family: c.sa_family_t, socktype: SockType) ?c_int { 44 | const fd = new_sock(family, socktype) orelse return null; 45 | setup_listen_sock(fd, family); 46 | return fd; 47 | } 48 | 49 | pub fn new_tcp_conn_sock(family: c.sa_family_t) ?c_int { 50 | const fd = new_sock(family, .tcp) orelse return null; 51 | setup_tcp_conn_sock(fd); 52 | return fd; 53 | } 54 | 55 | // =============================================================== 56 | 57 | /// `optname`: for printing 58 | pub noinline fn getsockopt_int(fd: c_int, level: c_int, opt: c_int, optname: cc.ConstStr) ?c_int { 59 | return cc.getsockopt_int(fd, level, opt) orelse { 60 | log.err(@src(), "getsockopt(%d, level:%d, opt:%s) failed: (%d) %m", .{ fd, level, optname, cc.errno() }); 61 | return null; 62 | }; 63 | } 64 | 65 | /// `optname`: for printing 66 | pub noinline fn setsockopt_int(fd: c_int, level: c_int, opt: c_int, optname: cc.ConstStr, value: c_int) ?void { 67 | return cc.setsockopt_int(fd, level, opt, value) orelse { 68 | log.err(@src(), "setsockopt(%d, level:%d, opt:%s, value:%d) failed: (%d) %m", .{ fd, level, optname, value, cc.errno() }); 69 | return null; 70 | }; 71 | } 72 | 73 | fn setup_listen_sock(fd: c_int, family: c.sa_family_t) void { 74 | _ = setsockopt_int(fd, c.SOL_SOCKET, c.SO_REUSEADDR, "SO_REUSEADDR", 1); 75 | 76 | if (g.flags.has(.reuse_port)) 77 | _ = setsockopt_int(fd, c.SOL_SOCKET, c.SO_REUSEPORT, "SO_REUSEPORT", 1); 78 | 79 | if (family == c.AF_INET6) 80 | _ = setsockopt_int(fd, c.IPPROTO_IPV6, c.IPV6_V6ONLY, "IPV6_V6ONLY", 0); 81 | } 82 | 83 | pub fn setup_tcp_conn_sock(fd: c_int) void { 84 | _ = setsockopt_int(fd, c.IPPROTO_TCP, c.TCP_NODELAY, "TCP_NODELAY", 1); 85 | 86 | _ = setsockopt_int(fd, c.SOL_SOCKET, c.SO_KEEPALIVE, "SO_KEEPALIVE", 1); 87 | _ = setsockopt_int(fd, c.IPPROTO_TCP, c.TCP_KEEPIDLE, "TCP_KEEPIDLE", 60); 88 | _ = setsockopt_int(fd, c.IPPROTO_TCP, c.TCP_KEEPCNT, "TCP_KEEPCNT", 3); 89 | _ = setsockopt_int(fd, c.IPPROTO_TCP, c.TCP_KEEPINTVL, "TCP_KEEPINTVL", 5); 90 | } 91 | -------------------------------------------------------------------------------- /src/opt.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const builtin = @import("builtin"); 3 | const build_opts = @import("build_opts"); 4 | const cc = @import("cc.zig"); 5 | const cfg = @import("cfg.zig"); 6 | 7 | const help = 8 | \\usage: relay 9 | \\ -V, --version show the version of the program 10 | \\ see https://github.com/zfl9/relay for more details 11 | ; 12 | 13 | const version: cc.ConstStr = b: { 14 | var target: [:0]const u8 = @tagName(builtin.cpu.arch) ++ "-" ++ @tagName(builtin.os.tag) ++ "-" ++ @tagName(builtin.abi); 15 | 16 | if (builtin.target.isGnuLibC()) 17 | target = target ++ std.fmt.comptimePrint(".{}", .{builtin.os.version_range.linux.glibc}); 18 | 19 | if (!std.mem.eql(u8, target, build_opts.target)) 20 | @compileError("target-triple mismatch: " ++ target ++ " != " ++ build_opts.target); 21 | 22 | const cpu_model = builtin.cpu.model.name; 23 | 24 | if (!std.mem.startsWith(u8, build_opts.cpu, cpu_model)) 25 | @compileError("cpu-model mismatch: " ++ cpu_model ++ " != " ++ build_opts.cpu); 26 | 27 | var version_info: [:0]const u8 = "relay " ++ build_opts.version ++ " " ++ build_opts.commit_id; 28 | if (build_opts.wolfssl) version_info = version_info ++ " | wolfssl " ++ build_opts.wolfssl_version; 29 | 30 | var in_protos: [:0]const u8 = "in_protos:"; 31 | if (build_opts.in_tproxy) in_protos = in_protos ++ " tproxy"; 32 | if (build_opts.in_socks) in_protos = in_protos ++ " socks"; 33 | if (build_opts.in_tlsproxy) in_protos = in_protos ++ " tlsproxy"; 34 | if (build_opts.in_trojan) in_protos = in_protos ++ " trojan"; 35 | 36 | var out_protos: [:0]const u8 = "out_protos:"; 37 | if (build_opts.out_raw) out_protos = out_protos ++ " raw"; 38 | if (build_opts.out_socks) out_protos = out_protos ++ " socks"; 39 | if (build_opts.out_tlsproxy) out_protos = out_protos ++ " tlsproxy"; 40 | if (build_opts.out_trojan) out_protos = out_protos ++ " trojan"; 41 | 42 | break :b std.fmt.comptimePrint("{s}\n{s}\n{s}\ntarget: {s}\ncpu: {s}\nmode: {s}\n{s}", .{ 43 | version_info, 44 | in_protos, 45 | out_protos, 46 | build_opts.target, 47 | build_opts.cpu, 48 | build_opts.mode, 49 | "https://github.com/zfl9/relay", 50 | }); 51 | }; 52 | 53 | pub fn parse() void { 54 | const argv = std.os.argv; 55 | if (argv.len != 2) { 56 | cc.printf("%s\n", .{help}); 57 | cc.exit(0); 58 | } 59 | const arg = cc.strslice_c(std.os.argv[1]); 60 | if (std.mem.startsWith(u8, arg, "-")) { 61 | if (std.mem.eql(u8, arg, "-V") or std.mem.eql(u8, arg, "--version")) 62 | cc.printf("%s\n", .{version}) 63 | else 64 | cc.printf("%s\n", .{help}); 65 | cc.exit(0); 66 | } else { 67 | cfg.load(arg); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/out.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const build_opts = @import("build_opts"); 3 | const cfg = @import("cfg.zig"); 4 | const log = @import("log.zig"); 5 | const cc = @import("cc.zig"); 6 | const g = @import("g.zig"); 7 | 8 | const modules = b: { 9 | const s = struct { 10 | const all_modules = .{ 11 | @import("out_raw.zig"), 12 | @import("out_socks.zig"), 13 | @import("out_tlsproxy.zig"), 14 | @import("out_trojan.zig"), 15 | }; 16 | fn is_enable(comptime mod: type) bool { 17 | return @field(build_opts, "out_" ++ mod.NAME); 18 | } 19 | fn len() comptime_int { 20 | var n = 0; 21 | for (all_modules) |mod| { 22 | if (is_enable(mod)) 23 | n += 1; 24 | } 25 | return n; 26 | } 27 | fn fill(comptime list: []type) void { 28 | var n = 0; 29 | for (all_modules) |mod| { 30 | if (is_enable(mod)) { 31 | list[n] = mod; 32 | n += 1; 33 | } 34 | } 35 | } 36 | }; 37 | var array: [s.len()]type = undefined; 38 | s.fill(&array); 39 | break :b array; 40 | }; 41 | 42 | pub const Config = b: { 43 | var enum_fields: [modules.len]std.builtin.Type.EnumField = undefined; 44 | var union_fields: [modules.len]std.builtin.Type.UnionField = undefined; 45 | var n = 0; 46 | for (modules) |mod| { 47 | enum_fields[n] = .{ 48 | .name = mod.NAME, 49 | .value = n, 50 | }; 51 | union_fields[n] = .{ 52 | .name = mod.NAME, 53 | .field_type = mod.Config, 54 | .alignment = @alignOf(mod.Config), 55 | }; 56 | n += 1; 57 | } 58 | break :b @Type(.{ .Union = .{ 59 | .layout = .Auto, 60 | .fields = union_fields[0..n], 61 | .decls = &.{}, 62 | .tag_type = @Type(.{ .Enum = .{ 63 | .layout = .Auto, 64 | .tag_type = u8, 65 | .fields = enum_fields[0..n], 66 | .decls = &.{}, 67 | .is_exhaustive = true, 68 | } }), 69 | } }); 70 | }; 71 | 72 | pub fn load_config(proto_name: []const u8, content: []const u8) ?Config { 73 | inline for (modules) |mod| { 74 | if (std.mem.eql(u8, proto_name, mod.NAME)) { 75 | return @unionInit( 76 | Config, 77 | mod.NAME, 78 | mod.Config.load(content) orelse return null, 79 | ); 80 | } 81 | } 82 | log.err(@src(), "unknown proto: %.*s", .{ cc.to_int(proto_name.len), proto_name.ptr }); 83 | return null; 84 | } 85 | -------------------------------------------------------------------------------- /src/out_raw.zig: -------------------------------------------------------------------------------- 1 | const cfg_loader = @import("cfg_loader.zig"); 2 | const cfg_checker = @import("cfg_checker.zig"); 3 | 4 | pub const NAME = "raw"; 5 | 6 | pub const Config = struct { 7 | _: bool = true, 8 | 9 | pub fn load(content: []const u8) ?Config { 10 | var self = Config{}; 11 | const src = @src(); 12 | cfg_loader.load(src, &self, content) orelse return null; 13 | return self; 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /src/out_socks.zig: -------------------------------------------------------------------------------- 1 | const cfg_loader = @import("cfg_loader.zig"); 2 | const cfg_checker = @import("cfg_checker.zig"); 3 | 4 | pub const NAME = "socks"; 5 | 6 | pub const Config = struct { 7 | passwd: [:0]const u8 = "", 8 | server: [:0]const u8 = "", 9 | port: u16 = 1080, 10 | tcp: bool = true, 11 | udp: bool = true, 12 | 13 | pub fn load(content: []const u8) ?Config { 14 | var self = Config{}; 15 | const src = @src(); 16 | cfg_loader.load(src, &self, content) orelse return null; 17 | cfg_checker.required(src, self.server.len > 0, "server") orelse return null; 18 | cfg_checker.check_port(src, self.port) orelse return null; 19 | cfg_checker.check_tcp_udp(src, self.tcp, self.udp) orelse return null; 20 | return self; 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /src/out_tlsproxy.zig: -------------------------------------------------------------------------------- 1 | const cfg_loader = @import("cfg_loader.zig"); 2 | const cfg_checker = @import("cfg_checker.zig"); 3 | 4 | pub const NAME = "tlsproxy"; 5 | 6 | pub const Config = struct { 7 | uri: [:0]const u8 = "", 8 | passwd: [:0]const u8 = "", 9 | server: [:0]const u8 = "", 10 | port: u16 = 443, 11 | tcp: bool = true, 12 | udp: bool = true, 13 | 14 | pub fn load(content: []const u8) ?Config { 15 | var self = Config{}; 16 | const src = @src(); 17 | cfg_loader.load(src, &self, content) orelse return null; 18 | cfg_checker.required(src, self.passwd.len > 0, "passwd") orelse return null; 19 | cfg_checker.required(src, self.server.len > 0, "server") orelse return null; 20 | cfg_checker.check_port(src, self.port) orelse return null; 21 | cfg_checker.check_tcp_udp(src, self.tcp, self.udp) orelse return null; 22 | self.set_default_value(); 23 | return self; 24 | } 25 | 26 | fn set_default_value(self: *Config) void { 27 | if (self.uri.len == 0) 28 | self.uri = "tls-proxy"; 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /src/out_trojan.zig: -------------------------------------------------------------------------------- 1 | const cfg_loader = @import("cfg_loader.zig"); 2 | const cfg_checker = @import("cfg_checker.zig"); 3 | 4 | pub const NAME = "trojan"; 5 | 6 | pub const Config = struct { 7 | passwd: [:0]const u8 = "", 8 | server: [:0]const u8 = "", 9 | port: u16 = 443, 10 | tcp: bool = true, 11 | udp: bool = true, 12 | 13 | pub fn load(content: []const u8) ?Config { 14 | var self = Config{}; 15 | const src = @src(); 16 | cfg_loader.load(src, &self, content) orelse return null; 17 | cfg_checker.required(src, self.passwd.len > 0, "passwd") orelse return null; 18 | cfg_checker.required(src, self.server.len > 0, "server") orelse return null; 19 | cfg_checker.check_port(src, self.port) orelse return null; 20 | cfg_checker.check_tcp_udp(src, self.tcp, self.udp) orelse return null; 21 | return self; 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /src/sentinel_vector.zig: -------------------------------------------------------------------------------- 1 | const cc = @import("cc.zig"); 2 | const g = @import("g.zig"); 3 | 4 | // ========================================== 5 | pub fn SentinelVector(comptime T: type, comptime sentinel: T) type { 6 | return struct { 7 | const Self = @This(); 8 | 9 | /// sentinel-terminated slice 10 | items: [:sentinel]T = &[_:sentinel]T{}, 11 | /// 0 means null, no memory allocated 12 | capacity: usize = 0, 13 | 14 | // ========================================== 15 | 16 | /// no memory allocated (initial state) 17 | pub fn is_null(self: *const Self) bool { 18 | return self.capacity == 0; 19 | } 20 | 21 | /// no items in the vector 22 | pub fn is_empty(self: *const Self) bool { 23 | return self.items.len == 0; 24 | } 25 | 26 | pub fn append(self: *Self) *T { 27 | self.resize(self.items.len + 1); 28 | return &self.items[self.items.len - 1]; 29 | } 30 | 31 | /// end with sentinel 32 | pub fn resize(self: *Self, n_items: usize) void { 33 | if (n_items + 1 > self.capacity) { 34 | const new_mem = g.allocator.realloc(self.mem(), n_items + 1) catch unreachable; 35 | self.set_mem(new_mem); 36 | } 37 | self.items.len = n_items; 38 | self.items[n_items] = sentinel; 39 | } 40 | 41 | fn mem(self: *const Self) []T { 42 | return self.items.ptr[0..self.capacity]; 43 | } 44 | 45 | fn set_mem(self: *Self, new_mem: []T) void { 46 | self.items.ptr = @ptrCast(@TypeOf(self.items.ptr), new_mem.ptr); 47 | self.capacity = new_mem.len; 48 | } 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /src/server.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const g = @import("g.zig"); 3 | const c = @import("c.zig"); 4 | const cc = @import("cc.zig"); 5 | const co = @import("co.zig"); 6 | const log = @import("log.zig"); 7 | const net = @import("net.zig"); 8 | const EvLoop = @import("EvLoop.zig"); 9 | const ListNode = @import("ListNode.zig"); 10 | const flags_op = @import("flags_op.zig"); 11 | const assert = std.debug.assert; 12 | 13 | pub fn start() void { 14 | // TODO 15 | log.info(@src(), "start ...", .{}); 16 | } 17 | -------------------------------------------------------------------------------- /src/str2int.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub fn parse(comptime T: type, str: []const u8, radix: u8) ?T { 4 | if (@bitSizeOf(T) < 1 or @bitSizeOf(T) > 64) 5 | @compileError("expected i1..i64 or s1..s64, found " ++ @typeName(T)); 6 | 7 | return @intCast(T, parse_internal( 8 | if (comptime std.meta.trait.isSignedInt(T)) i64 else u64, 9 | std.math.minInt(T), 10 | std.math.maxInt(T), 11 | str, 12 | radix, 13 | ) orelse return null); 14 | } 15 | 16 | fn parse_internal( 17 | comptime T: type, 18 | min_value: T, 19 | max_value: T, 20 | str: []const u8, 21 | radix: u8, 22 | ) ?T { 23 | const res = std.fmt.parseInt(T, str, radix) catch return null; 24 | if (res < min_value or res > max_value) 25 | return null; 26 | return res; 27 | } 28 | -------------------------------------------------------------------------------- /src/tests.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const cc = @import("cc.zig"); 3 | const modules = @import("modules.zig"); 4 | 5 | const TestFn = struct { 6 | name: [:0]const u8, 7 | func: std.meta.FnPtr(fn () anyerror!void), 8 | }; 9 | 10 | const all_test_fns = collect(0); 11 | 12 | fn collect(comptime _: comptime_int) [count()]TestFn { 13 | @setEvalBranchQuota(1000000); 14 | var test_fns: [count()]TestFn = undefined; 15 | var test_fn_i = 0; 16 | var module_idx = 0; 17 | for (modules.module_list) |module| { 18 | defer module_idx += 1; 19 | for (@typeInfo(module).Struct.decls) |decl| { 20 | if (std.mem.startsWith(u8, decl.name, "test: ")) { 21 | test_fns[test_fn_i] = .{ 22 | .name = modules.name_list[module_idx] ++ ": " ++ decl.name[6..], 23 | .func = @field(module, decl.name), 24 | }; 25 | test_fn_i += 1; 26 | } 27 | } 28 | } 29 | return test_fns; 30 | } 31 | 32 | fn count() comptime_int { 33 | @setEvalBranchQuota(1000000); 34 | var n = 0; 35 | for (modules.module_list) |module| { 36 | for (@typeInfo(module).Struct.decls) |decl| { 37 | if (std.mem.startsWith(u8, decl.name, "test: ")) 38 | n += 1; 39 | } 40 | } 41 | return n; 42 | } 43 | 44 | /// todo: support --test-filter [pattern] 45 | /// todo: implement std.testing.allocator 46 | pub fn main() u8 { 47 | if (all_test_fns.len <= 0) 48 | return 0; 49 | 50 | var ok_count: usize = 0; 51 | var skip_count: usize = 0; 52 | var failed_count: usize = 0; 53 | 54 | for (all_test_fns) |test_fn| { 55 | cc.printf_err("%s\n", .{test_fn.name.ptr}); 56 | 57 | if (nosuspend test_fn.func()) |_| { 58 | ok_count += 1; 59 | cc.printf_err("%-35s [\x1b[32;1mOK\x1b[0m]\n", .{test_fn.name.ptr}); 60 | } else |err| switch (err) { 61 | error.SkipZigTest => { 62 | skip_count += 1; 63 | cc.printf_err("%-35s [\x1b[34;1mSKIP\x1b[0m]\n", .{test_fn.name.ptr}); 64 | }, 65 | else => { 66 | failed_count += 1; 67 | cc.printf_err("\x1b[31;1merror: %s\x1b[0m\n", .{@errorName(err).ptr}); 68 | if (@errorReturnTrace()) |trace| 69 | std.debug.dumpStackTrace(trace.*); 70 | cc.printf_err("%-35s [\x1b[31;1mFAILED\x1b[0m]\n", .{test_fn.name.ptr}); 71 | }, 72 | } 73 | 74 | cc.printf_err("\n", .{}); 75 | } 76 | 77 | cc.printf_err("summary: %sOK: %zu\x1b[0m | %sSKIP: %zu\x1b[0m | %sFAILED: %zu\x1b[0m\n", .{ 78 | @as([:0]const u8, if (ok_count > 0) "\x1b[32;1m" else "").ptr, 79 | ok_count, 80 | @as([:0]const u8, if (skip_count > 0) "\x1b[34;1m" else "").ptr, 81 | skip_count, 82 | @as([:0]const u8, if (failed_count > 0) "\x1b[31;1m" else "").ptr, 83 | failed_count, 84 | }); 85 | 86 | return 0; 87 | } 88 | -------------------------------------------------------------------------------- /src/wolfssl.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef ENABLE_WOLFSSL 4 | 5 | #include "wolfssl_opt.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /src/wolfssl_opt.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define NO_WOLFSSL_SERVER 4 | #define WOLFSSL_NO_ATOMICS 5 | #define WOLFSSL_AEAD_ONLY 6 | #define LARGE_STATIC_BUFFERS 7 | -------------------------------------------------------------------------------- /zls.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "build_options": [ 3 | { 4 | "name": "mode", 5 | "value": "debug" 6 | }, 7 | { 8 | "name": "wolfssl" 9 | } 10 | ] 11 | } 12 | --------------------------------------------------------------------------------