mirror of
				https://github.com/optim-enterprises-bv/Xray-core.git
				synced 2025-10-31 18:47:52 +00:00 
			
		
		
		
	v1.0.0
This commit is contained in:
		
							
								
								
									
										373
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										373
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,373 @@ | |||||||
|  | Mozilla Public License Version 2.0 | ||||||
|  | ================================== | ||||||
|  |  | ||||||
|  | 1. Definitions | ||||||
|  | -------------- | ||||||
|  |  | ||||||
|  | 1.1. "Contributor" | ||||||
|  |     means each individual or legal entity that creates, contributes to | ||||||
|  |     the creation of, or owns Covered Software. | ||||||
|  |  | ||||||
|  | 1.2. "Contributor Version" | ||||||
|  |     means the combination of the Contributions of others (if any) used | ||||||
|  |     by a Contributor and that particular Contributor's Contribution. | ||||||
|  |  | ||||||
|  | 1.3. "Contribution" | ||||||
|  |     means Covered Software of a particular Contributor. | ||||||
|  |  | ||||||
|  | 1.4. "Covered Software" | ||||||
|  |     means Source Code Form to which the initial Contributor has attached | ||||||
|  |     the notice in Exhibit A, the Executable Form of such Source Code | ||||||
|  |     Form, and Modifications of such Source Code Form, in each case | ||||||
|  |     including portions thereof. | ||||||
|  |  | ||||||
|  | 1.5. "Incompatible With Secondary Licenses" | ||||||
|  |     means | ||||||
|  |  | ||||||
|  |     (a) that the initial Contributor has attached the notice described | ||||||
|  |         in Exhibit B to the Covered Software; or | ||||||
|  |  | ||||||
|  |     (b) that the Covered Software was made available under the terms of | ||||||
|  |         version 1.1 or earlier of the License, but not also under the | ||||||
|  |         terms of a Secondary License. | ||||||
|  |  | ||||||
|  | 1.6. "Executable Form" | ||||||
|  |     means any form of the work other than Source Code Form. | ||||||
|  |  | ||||||
|  | 1.7. "Larger Work" | ||||||
|  |     means a work that combines Covered Software with other material, in | ||||||
|  |     a separate file or files, that is not Covered Software. | ||||||
|  |  | ||||||
|  | 1.8. "License" | ||||||
|  |     means this document. | ||||||
|  |  | ||||||
|  | 1.9. "Licensable" | ||||||
|  |     means having the right to grant, to the maximum extent possible, | ||||||
|  |     whether at the time of the initial grant or subsequently, any and | ||||||
|  |     all of the rights conveyed by this License. | ||||||
|  |  | ||||||
|  | 1.10. "Modifications" | ||||||
|  |     means any of the following: | ||||||
|  |  | ||||||
|  |     (a) any file in Source Code Form that results from an addition to, | ||||||
|  |         deletion from, or modification of the contents of Covered | ||||||
|  |         Software; or | ||||||
|  |  | ||||||
|  |     (b) any new file in Source Code Form that contains any Covered | ||||||
|  |         Software. | ||||||
|  |  | ||||||
|  | 1.11. "Patent Claims" of a Contributor | ||||||
|  |     means any patent claim(s), including without limitation, method, | ||||||
|  |     process, and apparatus claims, in any patent Licensable by such | ||||||
|  |     Contributor that would be infringed, but for the grant of the | ||||||
|  |     License, by the making, using, selling, offering for sale, having | ||||||
|  |     made, import, or transfer of either its Contributions or its | ||||||
|  |     Contributor Version. | ||||||
|  |  | ||||||
|  | 1.12. "Secondary License" | ||||||
|  |     means either the GNU General Public License, Version 2.0, the GNU | ||||||
|  |     Lesser General Public License, Version 2.1, the GNU Affero General | ||||||
|  |     Public License, Version 3.0, or any later versions of those | ||||||
|  |     licenses. | ||||||
|  |  | ||||||
|  | 1.13. "Source Code Form" | ||||||
|  |     means the form of the work preferred for making modifications. | ||||||
|  |  | ||||||
|  | 1.14. "You" (or "Your") | ||||||
|  |     means an individual or a legal entity exercising rights under this | ||||||
|  |     License. For legal entities, "You" includes any entity that | ||||||
|  |     controls, is controlled by, or is under common control with You. For | ||||||
|  |     purposes of this definition, "control" means (a) the power, direct | ||||||
|  |     or indirect, to cause the direction or management of such entity, | ||||||
|  |     whether by contract or otherwise, or (b) ownership of more than | ||||||
|  |     fifty percent (50%) of the outstanding shares or beneficial | ||||||
|  |     ownership of such entity. | ||||||
|  |  | ||||||
|  | 2. License Grants and Conditions | ||||||
|  | -------------------------------- | ||||||
|  |  | ||||||
|  | 2.1. Grants | ||||||
|  |  | ||||||
|  | Each Contributor hereby grants You a world-wide, royalty-free, | ||||||
|  | non-exclusive license: | ||||||
|  |  | ||||||
|  | (a) under intellectual property rights (other than patent or trademark) | ||||||
|  |     Licensable by such Contributor to use, reproduce, make available, | ||||||
|  |     modify, display, perform, distribute, and otherwise exploit its | ||||||
|  |     Contributions, either on an unmodified basis, with Modifications, or | ||||||
|  |     as part of a Larger Work; and | ||||||
|  |  | ||||||
|  | (b) under Patent Claims of such Contributor to make, use, sell, offer | ||||||
|  |     for sale, have made, import, and otherwise transfer either its | ||||||
|  |     Contributions or its Contributor Version. | ||||||
|  |  | ||||||
|  | 2.2. Effective Date | ||||||
|  |  | ||||||
|  | The licenses granted in Section 2.1 with respect to any Contribution | ||||||
|  | become effective for each Contribution on the date the Contributor first | ||||||
|  | distributes such Contribution. | ||||||
|  |  | ||||||
|  | 2.3. Limitations on Grant Scope | ||||||
|  |  | ||||||
|  | The licenses granted in this Section 2 are the only rights granted under | ||||||
|  | this License. No additional rights or licenses will be implied from the | ||||||
|  | distribution or licensing of Covered Software under this License. | ||||||
|  | Notwithstanding Section 2.1(b) above, no patent license is granted by a | ||||||
|  | Contributor: | ||||||
|  |  | ||||||
|  | (a) for any code that a Contributor has removed from Covered Software; | ||||||
|  |     or | ||||||
|  |  | ||||||
|  | (b) for infringements caused by: (i) Your and any other third party's | ||||||
|  |     modifications of Covered Software, or (ii) the combination of its | ||||||
|  |     Contributions with other software (except as part of its Contributor | ||||||
|  |     Version); or | ||||||
|  |  | ||||||
|  | (c) under Patent Claims infringed by Covered Software in the absence of | ||||||
|  |     its Contributions. | ||||||
|  |  | ||||||
|  | This License does not grant any rights in the trademarks, service marks, | ||||||
|  | or logos of any Contributor (except as may be necessary to comply with | ||||||
|  | the notice requirements in Section 3.4). | ||||||
|  |  | ||||||
|  | 2.4. Subsequent Licenses | ||||||
|  |  | ||||||
|  | No Contributor makes additional grants as a result of Your choice to | ||||||
|  | distribute the Covered Software under a subsequent version of this | ||||||
|  | License (see Section 10.2) or under the terms of a Secondary License (if | ||||||
|  | permitted under the terms of Section 3.3). | ||||||
|  |  | ||||||
|  | 2.5. Representation | ||||||
|  |  | ||||||
|  | Each Contributor represents that the Contributor believes its | ||||||
|  | Contributions are its original creation(s) or it has sufficient rights | ||||||
|  | to grant the rights to its Contributions conveyed by this License. | ||||||
|  |  | ||||||
|  | 2.6. Fair Use | ||||||
|  |  | ||||||
|  | This License is not intended to limit any rights You have under | ||||||
|  | applicable copyright doctrines of fair use, fair dealing, or other | ||||||
|  | equivalents. | ||||||
|  |  | ||||||
|  | 2.7. Conditions | ||||||
|  |  | ||||||
|  | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted | ||||||
|  | in Section 2.1. | ||||||
|  |  | ||||||
|  | 3. Responsibilities | ||||||
|  | ------------------- | ||||||
|  |  | ||||||
|  | 3.1. Distribution of Source Form | ||||||
|  |  | ||||||
|  | All distribution of Covered Software in Source Code Form, including any | ||||||
|  | Modifications that You create or to which You contribute, must be under | ||||||
|  | the terms of this License. You must inform recipients that the Source | ||||||
|  | Code Form of the Covered Software is governed by the terms of this | ||||||
|  | License, and how they can obtain a copy of this License. You may not | ||||||
|  | attempt to alter or restrict the recipients' rights in the Source Code | ||||||
|  | Form. | ||||||
|  |  | ||||||
|  | 3.2. Distribution of Executable Form | ||||||
|  |  | ||||||
|  | If You distribute Covered Software in Executable Form then: | ||||||
|  |  | ||||||
|  | (a) such Covered Software must also be made available in Source Code | ||||||
|  |     Form, as described in Section 3.1, and You must inform recipients of | ||||||
|  |     the Executable Form how they can obtain a copy of such Source Code | ||||||
|  |     Form by reasonable means in a timely manner, at a charge no more | ||||||
|  |     than the cost of distribution to the recipient; and | ||||||
|  |  | ||||||
|  | (b) You may distribute such Executable Form under the terms of this | ||||||
|  |     License, or sublicense it under different terms, provided that the | ||||||
|  |     license for the Executable Form does not attempt to limit or alter | ||||||
|  |     the recipients' rights in the Source Code Form under this License. | ||||||
|  |  | ||||||
|  | 3.3. Distribution of a Larger Work | ||||||
|  |  | ||||||
|  | You may create and distribute a Larger Work under terms of Your choice, | ||||||
|  | provided that You also comply with the requirements of this License for | ||||||
|  | the Covered Software. If the Larger Work is a combination of Covered | ||||||
|  | Software with a work governed by one or more Secondary Licenses, and the | ||||||
|  | Covered Software is not Incompatible With Secondary Licenses, this | ||||||
|  | License permits You to additionally distribute such Covered Software | ||||||
|  | under the terms of such Secondary License(s), so that the recipient of | ||||||
|  | the Larger Work may, at their option, further distribute the Covered | ||||||
|  | Software under the terms of either this License or such Secondary | ||||||
|  | License(s). | ||||||
|  |  | ||||||
|  | 3.4. Notices | ||||||
|  |  | ||||||
|  | You may not remove or alter the substance of any license notices | ||||||
|  | (including copyright notices, patent notices, disclaimers of warranty, | ||||||
|  | or limitations of liability) contained within the Source Code Form of | ||||||
|  | the Covered Software, except that You may alter any license notices to | ||||||
|  | the extent required to remedy known factual inaccuracies. | ||||||
|  |  | ||||||
|  | 3.5. Application of Additional Terms | ||||||
|  |  | ||||||
|  | You may choose to offer, and to charge a fee for, warranty, support, | ||||||
|  | indemnity or liability obligations to one or more recipients of Covered | ||||||
|  | Software. However, You may do so only on Your own behalf, and not on | ||||||
|  | behalf of any Contributor. You must make it absolutely clear that any | ||||||
|  | such warranty, support, indemnity, or liability obligation is offered by | ||||||
|  | You alone, and You hereby agree to indemnify every Contributor for any | ||||||
|  | liability incurred by such Contributor as a result of warranty, support, | ||||||
|  | indemnity or liability terms You offer. You may include additional | ||||||
|  | disclaimers of warranty and limitations of liability specific to any | ||||||
|  | jurisdiction. | ||||||
|  |  | ||||||
|  | 4. Inability to Comply Due to Statute or Regulation | ||||||
|  | --------------------------------------------------- | ||||||
|  |  | ||||||
|  | If it is impossible for You to comply with any of the terms of this | ||||||
|  | License with respect to some or all of the Covered Software due to | ||||||
|  | statute, judicial order, or regulation then You must: (a) comply with | ||||||
|  | the terms of this License to the maximum extent possible; and (b) | ||||||
|  | describe the limitations and the code they affect. Such description must | ||||||
|  | be placed in a text file included with all distributions of the Covered | ||||||
|  | Software under this License. Except to the extent prohibited by statute | ||||||
|  | or regulation, such description must be sufficiently detailed for a | ||||||
|  | recipient of ordinary skill to be able to understand it. | ||||||
|  |  | ||||||
|  | 5. Termination | ||||||
|  | -------------- | ||||||
|  |  | ||||||
|  | 5.1. The rights granted under this License will terminate automatically | ||||||
|  | if You fail to comply with any of its terms. However, if You become | ||||||
|  | compliant, then the rights granted under this License from a particular | ||||||
|  | Contributor are reinstated (a) provisionally, unless and until such | ||||||
|  | Contributor explicitly and finally terminates Your grants, and (b) on an | ||||||
|  | ongoing basis, if such Contributor fails to notify You of the | ||||||
|  | non-compliance by some reasonable means prior to 60 days after You have | ||||||
|  | come back into compliance. Moreover, Your grants from a particular | ||||||
|  | Contributor are reinstated on an ongoing basis if such Contributor | ||||||
|  | notifies You of the non-compliance by some reasonable means, this is the | ||||||
|  | first time You have received notice of non-compliance with this License | ||||||
|  | from such Contributor, and You become compliant prior to 30 days after | ||||||
|  | Your receipt of the notice. | ||||||
|  |  | ||||||
|  | 5.2. If You initiate litigation against any entity by asserting a patent | ||||||
|  | infringement claim (excluding declaratory judgment actions, | ||||||
|  | counter-claims, and cross-claims) alleging that a Contributor Version | ||||||
|  | directly or indirectly infringes any patent, then the rights granted to | ||||||
|  | You by any and all Contributors for the Covered Software under Section | ||||||
|  | 2.1 of this License shall terminate. | ||||||
|  |  | ||||||
|  | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all | ||||||
|  | end user license agreements (excluding distributors and resellers) which | ||||||
|  | have been validly granted by You or Your distributors under this License | ||||||
|  | prior to termination shall survive termination. | ||||||
|  |  | ||||||
|  | ************************************************************************ | ||||||
|  | *                                                                      * | ||||||
|  | *  6. Disclaimer of Warranty                                           * | ||||||
|  | *  -------------------------                                           * | ||||||
|  | *                                                                      * | ||||||
|  | *  Covered Software is provided under this License on an "as is"       * | ||||||
|  | *  basis, without warranty of any kind, either expressed, implied, or  * | ||||||
|  | *  statutory, including, without limitation, warranties that the       * | ||||||
|  | *  Covered Software is free of defects, merchantable, fit for a        * | ||||||
|  | *  particular purpose or non-infringing. The entire risk as to the     * | ||||||
|  | *  quality and performance of the Covered Software is with You.        * | ||||||
|  | *  Should any Covered Software prove defective in any respect, You     * | ||||||
|  | *  (not any Contributor) assume the cost of any necessary servicing,   * | ||||||
|  | *  repair, or correction. This disclaimer of warranty constitutes an   * | ||||||
|  | *  essential part of this License. No use of any Covered Software is   * | ||||||
|  | *  authorized under this License except under this disclaimer.         * | ||||||
|  | *                                                                      * | ||||||
|  | ************************************************************************ | ||||||
|  |  | ||||||
|  | ************************************************************************ | ||||||
|  | *                                                                      * | ||||||
|  | *  7. Limitation of Liability                                          * | ||||||
|  | *  --------------------------                                          * | ||||||
|  | *                                                                      * | ||||||
|  | *  Under no circumstances and under no legal theory, whether tort      * | ||||||
|  | *  (including negligence), contract, or otherwise, shall any           * | ||||||
|  | *  Contributor, or anyone who distributes Covered Software as          * | ||||||
|  | *  permitted above, be liable to You for any direct, indirect,         * | ||||||
|  | *  special, incidental, or consequential damages of any character      * | ||||||
|  | *  including, without limitation, damages for lost profits, loss of    * | ||||||
|  | *  goodwill, work stoppage, computer failure or malfunction, or any    * | ||||||
|  | *  and all other commercial damages or losses, even if such party      * | ||||||
|  | *  shall have been informed of the possibility of such damages. This   * | ||||||
|  | *  limitation of liability shall not apply to liability for death or   * | ||||||
|  | *  personal injury resulting from such party's negligence to the       * | ||||||
|  | *  extent applicable law prohibits such limitation. Some               * | ||||||
|  | *  jurisdictions do not allow the exclusion or limitation of           * | ||||||
|  | *  incidental or consequential damages, so this exclusion and          * | ||||||
|  | *  limitation may not apply to You.                                    * | ||||||
|  | *                                                                      * | ||||||
|  | ************************************************************************ | ||||||
|  |  | ||||||
|  | 8. Litigation | ||||||
|  | ------------- | ||||||
|  |  | ||||||
|  | Any litigation relating to this License may be brought only in the | ||||||
|  | courts of a jurisdiction where the defendant maintains its principal | ||||||
|  | place of business and such litigation shall be governed by laws of that | ||||||
|  | jurisdiction, without reference to its conflict-of-law provisions. | ||||||
|  | Nothing in this Section shall prevent a party's ability to bring | ||||||
|  | cross-claims or counter-claims. | ||||||
|  |  | ||||||
|  | 9. Miscellaneous | ||||||
|  | ---------------- | ||||||
|  |  | ||||||
|  | This License represents the complete agreement concerning the subject | ||||||
|  | matter hereof. If any provision of this License is held to be | ||||||
|  | unenforceable, such provision shall be reformed only to the extent | ||||||
|  | necessary to make it enforceable. Any law or regulation which provides | ||||||
|  | that the language of a contract shall be construed against the drafter | ||||||
|  | shall not be used to construe this License against a Contributor. | ||||||
|  |  | ||||||
|  | 10. Versions of the License | ||||||
|  | --------------------------- | ||||||
|  |  | ||||||
|  | 10.1. New Versions | ||||||
|  |  | ||||||
|  | Mozilla Foundation is the license steward. Except as provided in Section | ||||||
|  | 10.3, no one other than the license steward has the right to modify or | ||||||
|  | publish new versions of this License. Each version will be given a | ||||||
|  | distinguishing version number. | ||||||
|  |  | ||||||
|  | 10.2. Effect of New Versions | ||||||
|  |  | ||||||
|  | You may distribute the Covered Software under the terms of the version | ||||||
|  | of the License under which You originally received the Covered Software, | ||||||
|  | or under the terms of any subsequent version published by the license | ||||||
|  | steward. | ||||||
|  |  | ||||||
|  | 10.3. Modified Versions | ||||||
|  |  | ||||||
|  | If you create software not governed by this License, and you want to | ||||||
|  | create a new license for such software, you may create and use a | ||||||
|  | modified version of this License if you rename the license and remove | ||||||
|  | any references to the name of the license steward (except to note that | ||||||
|  | such modified license differs from this License). | ||||||
|  |  | ||||||
|  | 10.4. Distributing Source Code Form that is Incompatible With Secondary | ||||||
|  | Licenses | ||||||
|  |  | ||||||
|  | If You choose to distribute Source Code Form that is Incompatible With | ||||||
|  | Secondary Licenses under the terms of this version of the License, the | ||||||
|  | notice described in Exhibit B of this License must be attached. | ||||||
|  |  | ||||||
|  | Exhibit A - Source Code Form License Notice | ||||||
|  | ------------------------------------------- | ||||||
|  |  | ||||||
|  |   This Source Code Form is subject to the terms of the Mozilla Public | ||||||
|  |   License, v. 2.0. If a copy of the MPL was not distributed with this | ||||||
|  |   file, You can obtain one at http://mozilla.org/MPL/2.0/. | ||||||
|  |  | ||||||
|  | If it is not possible or desirable to put the notice in a particular | ||||||
|  | file, then You may include the notice in a location (such as a LICENSE | ||||||
|  | file in a relevant directory) where a recipient would be likely to look | ||||||
|  | for such a notice. | ||||||
|  |  | ||||||
|  | You may add additional accurate notices of copyright ownership. | ||||||
|  |  | ||||||
|  | Exhibit B - "Incompatible With Secondary Licenses" Notice | ||||||
|  | --------------------------------------------------------- | ||||||
|  |  | ||||||
|  |   This Source Code Form is "Incompatible With Secondary Licenses", as | ||||||
|  |   defined by the Mozilla Public License, v. 2.0. | ||||||
							
								
								
									
										54
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										54
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,2 +1,52 @@ | |||||||
| # X-ray | # Project X | ||||||
| X-ray, Penetrate GFWs. The best v2ray-core, with XTLS support. Automatically patch and compile by GitHub Actions, fully compatible configuration. |  | ||||||
|  | [Project X](https://github.com/XTLS) originates from XTLS protocol, provides a set of network tools such as [Xray-core](https://github.com/XTLS/Xray-core) and [Xray-flutter](https://github.com/XTLS/Xray-flutter). | ||||||
|  |  | ||||||
|  | ## Installation | ||||||
|  |  | ||||||
|  | - Linux script | ||||||
|  |   - [Xray-install](https://github.com/XTLS/Xray-install) | ||||||
|  |  | ||||||
|  | ## Usage | ||||||
|  |  | ||||||
|  | [Xray-examples](https://github.com/XTLS/Xray-examples) / [VLESS-TCP-XTLS-WHATEVER](https://github.com/XTLS/Xray-examples/tree/main/VLESS-TCP-XTLS-WHATEVER) | ||||||
|  |  | ||||||
|  | ## License | ||||||
|  |  | ||||||
|  | [Mozilla Public License Version 2.0](https://github.com/XTLS/Xray-core/main/LICENSE) | ||||||
|  |  | ||||||
|  | ## Credits | ||||||
|  |  | ||||||
|  | This repo relies on the following third-party projects: | ||||||
|  |  | ||||||
|  | - Special thanks: | ||||||
|  |   - [v2fly/v2ray-core](https://github.com/v2fly/v2ray-core) | ||||||
|  | - In production: | ||||||
|  |   - [gorilla/websocket](https://github.com/gorilla/websocket) | ||||||
|  |   - [lucas-clemente/quic-go](https://github.com/lucas-clemente/quic-go) | ||||||
|  |   - [pires/go-proxyproto](https://github.com/pires/go-proxyproto) | ||||||
|  |   - [seiflotfy/cuckoofilter](https://github.com/seiflotfy/cuckoofilter) | ||||||
|  |   - [google/starlark-go](https://github.com/google/starlark-go) | ||||||
|  | - For testing only: | ||||||
|  |   - [miekg/dns](https://github.com/miekg/dns) | ||||||
|  |   - [h12w/socks](https://github.com/h12w/socks) | ||||||
|  |  | ||||||
|  | ## Compilation | ||||||
|  |  | ||||||
|  | ### Windows | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | go build -o xray.exe -trimpath -ldflags "-s -w -buildid=" ./main | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### Linux / macOS | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | go build -o xray -trimpath -ldflags "-s -w -buildid=" ./main | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## Telegram | ||||||
|  |  | ||||||
|  | [Project X](https://t.me/projectXray) | ||||||
|  |  | ||||||
|  | [Project X Channel](https://t.me/projectXtls) | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								app/app.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								app/app.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | |||||||
|  | // Package app contains feature implementations of Xray. The features may be enabled during runtime. | ||||||
|  | package app | ||||||
							
								
								
									
										110
									
								
								app/commander/commander.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								app/commander/commander.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,110 @@ | |||||||
|  | // +build !confonly | ||||||
|  |  | ||||||
|  | package commander | ||||||
|  |  | ||||||
|  | //go:generate go run github.com/xtls/xray-core/v1/common/errors/errorgen | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"net" | ||||||
|  | 	"sync" | ||||||
|  |  | ||||||
|  | 	"google.golang.org/grpc" | ||||||
|  |  | ||||||
|  | 	"github.com/xtls/xray-core/v1/common" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/signal/done" | ||||||
|  | 	core "github.com/xtls/xray-core/v1/core" | ||||||
|  | 	"github.com/xtls/xray-core/v1/features/outbound" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Commander is a Xray feature that provides gRPC methods to external clients. | ||||||
|  | type Commander struct { | ||||||
|  | 	sync.Mutex | ||||||
|  | 	server   *grpc.Server | ||||||
|  | 	services []Service | ||||||
|  | 	ohm      outbound.Manager | ||||||
|  | 	tag      string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // NewCommander creates a new Commander based on the given config. | ||||||
|  | func NewCommander(ctx context.Context, config *Config) (*Commander, error) { | ||||||
|  | 	c := &Commander{ | ||||||
|  | 		tag: config.Tag, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	common.Must(core.RequireFeatures(ctx, func(om outbound.Manager) { | ||||||
|  | 		c.ohm = om | ||||||
|  | 	})) | ||||||
|  |  | ||||||
|  | 	for _, rawConfig := range config.Service { | ||||||
|  | 		config, err := rawConfig.GetInstance() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		rawService, err := common.CreateObject(ctx, config) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		service, ok := rawService.(Service) | ||||||
|  | 		if !ok { | ||||||
|  | 			return nil, newError("not a Service.") | ||||||
|  | 		} | ||||||
|  | 		c.services = append(c.services, service) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return c, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Type implements common.HasType. | ||||||
|  | func (c *Commander) Type() interface{} { | ||||||
|  | 	return (*Commander)(nil) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Start implements common.Runnable. | ||||||
|  | func (c *Commander) Start() error { | ||||||
|  | 	c.Lock() | ||||||
|  | 	c.server = grpc.NewServer() | ||||||
|  | 	for _, service := range c.services { | ||||||
|  | 		service.Register(c.server) | ||||||
|  | 	} | ||||||
|  | 	c.Unlock() | ||||||
|  |  | ||||||
|  | 	listener := &OutboundListener{ | ||||||
|  | 		buffer: make(chan net.Conn, 4), | ||||||
|  | 		done:   done.New(), | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	go func() { | ||||||
|  | 		if err := c.server.Serve(listener); err != nil { | ||||||
|  | 			newError("failed to start grpc server").Base(err).AtError().WriteToLog() | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  |  | ||||||
|  | 	if err := c.ohm.RemoveHandler(context.Background(), c.tag); err != nil { | ||||||
|  | 		newError("failed to remove existing handler").WriteToLog() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return c.ohm.AddHandler(context.Background(), &Outbound{ | ||||||
|  | 		tag:      c.tag, | ||||||
|  | 		listener: listener, | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Close implements common.Closable. | ||||||
|  | func (c *Commander) Close() error { | ||||||
|  | 	c.Lock() | ||||||
|  | 	defer c.Unlock() | ||||||
|  |  | ||||||
|  | 	if c.server != nil { | ||||||
|  | 		c.server.Stop() | ||||||
|  | 		c.server = nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func init() { | ||||||
|  | 	common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, cfg interface{}) (interface{}, error) { | ||||||
|  | 		return NewCommander(ctx, cfg.(*Config)) | ||||||
|  | 	})) | ||||||
|  | } | ||||||
							
								
								
									
										227
									
								
								app/commander/config.pb.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										227
									
								
								app/commander/config.pb.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,227 @@ | |||||||
|  | // Code generated by protoc-gen-go. DO NOT EDIT. | ||||||
|  | // versions: | ||||||
|  | // 	protoc-gen-go v1.25.0 | ||||||
|  | // 	protoc        v3.14.0 | ||||||
|  | // source: app/commander/config.proto | ||||||
|  |  | ||||||
|  | package commander | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	proto "github.com/golang/protobuf/proto" | ||||||
|  | 	serial "github.com/xtls/xray-core/v1/common/serial" | ||||||
|  | 	protoreflect "google.golang.org/protobuf/reflect/protoreflect" | ||||||
|  | 	protoimpl "google.golang.org/protobuf/runtime/protoimpl" | ||||||
|  | 	reflect "reflect" | ||||||
|  | 	sync "sync" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	// Verify that this generated code is sufficiently up-to-date. | ||||||
|  | 	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) | ||||||
|  | 	// Verify that runtime/protoimpl is sufficiently up-to-date. | ||||||
|  | 	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // This is a compile-time assertion that a sufficiently up-to-date version | ||||||
|  | // of the legacy proto package is being used. | ||||||
|  | const _ = proto.ProtoPackageIsVersion4 | ||||||
|  |  | ||||||
|  | // Config is the settings for Commander. | ||||||
|  | type Config struct { | ||||||
|  | 	state         protoimpl.MessageState | ||||||
|  | 	sizeCache     protoimpl.SizeCache | ||||||
|  | 	unknownFields protoimpl.UnknownFields | ||||||
|  |  | ||||||
|  | 	// Tag of the outbound handler that handles grpc connections. | ||||||
|  | 	Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"` | ||||||
|  | 	// Services that supported by this server. All services must implement Service | ||||||
|  | 	// interface. | ||||||
|  | 	Service []*serial.TypedMessage `protobuf:"bytes,2,rep,name=service,proto3" json:"service,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Config) Reset() { | ||||||
|  | 	*x = Config{} | ||||||
|  | 	if protoimpl.UnsafeEnabled { | ||||||
|  | 		mi := &file_app_commander_config_proto_msgTypes[0] | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		ms.StoreMessageInfo(mi) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Config) String() string { | ||||||
|  | 	return protoimpl.X.MessageStringOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (*Config) ProtoMessage() {} | ||||||
|  |  | ||||||
|  | func (x *Config) ProtoReflect() protoreflect.Message { | ||||||
|  | 	mi := &file_app_commander_config_proto_msgTypes[0] | ||||||
|  | 	if protoimpl.UnsafeEnabled && x != nil { | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		if ms.LoadMessageInfo() == nil { | ||||||
|  | 			ms.StoreMessageInfo(mi) | ||||||
|  | 		} | ||||||
|  | 		return ms | ||||||
|  | 	} | ||||||
|  | 	return mi.MessageOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Deprecated: Use Config.ProtoReflect.Descriptor instead. | ||||||
|  | func (*Config) Descriptor() ([]byte, []int) { | ||||||
|  | 	return file_app_commander_config_proto_rawDescGZIP(), []int{0} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Config) GetTag() string { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Tag | ||||||
|  | 	} | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Config) GetService() []*serial.TypedMessage { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Service | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ReflectionConfig is the placeholder config for ReflectionService. | ||||||
|  | type ReflectionConfig struct { | ||||||
|  | 	state         protoimpl.MessageState | ||||||
|  | 	sizeCache     protoimpl.SizeCache | ||||||
|  | 	unknownFields protoimpl.UnknownFields | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *ReflectionConfig) Reset() { | ||||||
|  | 	*x = ReflectionConfig{} | ||||||
|  | 	if protoimpl.UnsafeEnabled { | ||||||
|  | 		mi := &file_app_commander_config_proto_msgTypes[1] | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		ms.StoreMessageInfo(mi) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *ReflectionConfig) String() string { | ||||||
|  | 	return protoimpl.X.MessageStringOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (*ReflectionConfig) ProtoMessage() {} | ||||||
|  |  | ||||||
|  | func (x *ReflectionConfig) ProtoReflect() protoreflect.Message { | ||||||
|  | 	mi := &file_app_commander_config_proto_msgTypes[1] | ||||||
|  | 	if protoimpl.UnsafeEnabled && x != nil { | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		if ms.LoadMessageInfo() == nil { | ||||||
|  | 			ms.StoreMessageInfo(mi) | ||||||
|  | 		} | ||||||
|  | 		return ms | ||||||
|  | 	} | ||||||
|  | 	return mi.MessageOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Deprecated: Use ReflectionConfig.ProtoReflect.Descriptor instead. | ||||||
|  | func (*ReflectionConfig) Descriptor() ([]byte, []int) { | ||||||
|  | 	return file_app_commander_config_proto_rawDescGZIP(), []int{1} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var File_app_commander_config_proto protoreflect.FileDescriptor | ||||||
|  |  | ||||||
|  | var file_app_commander_config_proto_rawDesc = []byte{ | ||||||
|  | 	0x0a, 0x1a, 0x61, 0x70, 0x70, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x65, 0x72, 0x2f, | ||||||
|  | 	0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x78, 0x72, | ||||||
|  | 	0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x65, 0x72, | ||||||
|  | 	0x1a, 0x21, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2f, | ||||||
|  | 	0x74, 0x79, 0x70, 0x65, 0x64, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, | ||||||
|  | 	0x6f, 0x74, 0x6f, 0x22, 0x56, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x10, 0x0a, | ||||||
|  | 	0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, | ||||||
|  | 	0x3a, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, | ||||||
|  | 	0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, | ||||||
|  | 	0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, | ||||||
|  | 	0x67, 0x65, 0x52, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x22, 0x12, 0x0a, 0x10, 0x52, | ||||||
|  | 	0x65, 0x66, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, | ||||||
|  | 	0x5b, 0x0a, 0x16, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, | ||||||
|  | 	0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x65, 0x72, 0x50, 0x01, 0x5a, 0x2a, 0x67, 0x69, 0x74, | ||||||
|  | 	0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, | ||||||
|  | 	0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x63, 0x6f, | ||||||
|  | 	0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x65, 0x72, 0xaa, 0x02, 0x12, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x41, | ||||||
|  | 	0x70, 0x70, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, | ||||||
|  | 	0x6f, 0x74, 0x6f, 0x33, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var ( | ||||||
|  | 	file_app_commander_config_proto_rawDescOnce sync.Once | ||||||
|  | 	file_app_commander_config_proto_rawDescData = file_app_commander_config_proto_rawDesc | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func file_app_commander_config_proto_rawDescGZIP() []byte { | ||||||
|  | 	file_app_commander_config_proto_rawDescOnce.Do(func() { | ||||||
|  | 		file_app_commander_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_app_commander_config_proto_rawDescData) | ||||||
|  | 	}) | ||||||
|  | 	return file_app_commander_config_proto_rawDescData | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var file_app_commander_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2) | ||||||
|  | var file_app_commander_config_proto_goTypes = []interface{}{ | ||||||
|  | 	(*Config)(nil),              // 0: xray.app.commander.Config | ||||||
|  | 	(*ReflectionConfig)(nil),    // 1: xray.app.commander.ReflectionConfig | ||||||
|  | 	(*serial.TypedMessage)(nil), // 2: xray.common.serial.TypedMessage | ||||||
|  | } | ||||||
|  | var file_app_commander_config_proto_depIdxs = []int32{ | ||||||
|  | 	2, // 0: xray.app.commander.Config.service:type_name -> xray.common.serial.TypedMessage | ||||||
|  | 	1, // [1:1] is the sub-list for method output_type | ||||||
|  | 	1, // [1:1] is the sub-list for method input_type | ||||||
|  | 	1, // [1:1] is the sub-list for extension type_name | ||||||
|  | 	1, // [1:1] is the sub-list for extension extendee | ||||||
|  | 	0, // [0:1] is the sub-list for field type_name | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func init() { file_app_commander_config_proto_init() } | ||||||
|  | func file_app_commander_config_proto_init() { | ||||||
|  | 	if File_app_commander_config_proto != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if !protoimpl.UnsafeEnabled { | ||||||
|  | 		file_app_commander_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { | ||||||
|  | 			switch v := v.(*Config); i { | ||||||
|  | 			case 0: | ||||||
|  | 				return &v.state | ||||||
|  | 			case 1: | ||||||
|  | 				return &v.sizeCache | ||||||
|  | 			case 2: | ||||||
|  | 				return &v.unknownFields | ||||||
|  | 			default: | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		file_app_commander_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { | ||||||
|  | 			switch v := v.(*ReflectionConfig); i { | ||||||
|  | 			case 0: | ||||||
|  | 				return &v.state | ||||||
|  | 			case 1: | ||||||
|  | 				return &v.sizeCache | ||||||
|  | 			case 2: | ||||||
|  | 				return &v.unknownFields | ||||||
|  | 			default: | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	type x struct{} | ||||||
|  | 	out := protoimpl.TypeBuilder{ | ||||||
|  | 		File: protoimpl.DescBuilder{ | ||||||
|  | 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(), | ||||||
|  | 			RawDescriptor: file_app_commander_config_proto_rawDesc, | ||||||
|  | 			NumEnums:      0, | ||||||
|  | 			NumMessages:   2, | ||||||
|  | 			NumExtensions: 0, | ||||||
|  | 			NumServices:   0, | ||||||
|  | 		}, | ||||||
|  | 		GoTypes:           file_app_commander_config_proto_goTypes, | ||||||
|  | 		DependencyIndexes: file_app_commander_config_proto_depIdxs, | ||||||
|  | 		MessageInfos:      file_app_commander_config_proto_msgTypes, | ||||||
|  | 	}.Build() | ||||||
|  | 	File_app_commander_config_proto = out.File | ||||||
|  | 	file_app_commander_config_proto_rawDesc = nil | ||||||
|  | 	file_app_commander_config_proto_goTypes = nil | ||||||
|  | 	file_app_commander_config_proto_depIdxs = nil | ||||||
|  | } | ||||||
							
								
								
									
										21
									
								
								app/commander/config.proto
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								app/commander/config.proto
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | |||||||
|  | syntax = "proto3"; | ||||||
|  |  | ||||||
|  | package xray.app.commander; | ||||||
|  | option csharp_namespace = "Xray.App.Commander"; | ||||||
|  | option go_package = "github.com/xtls/xray-core/v1/app/commander"; | ||||||
|  | option java_package = "com.xray.app.commander"; | ||||||
|  | option java_multiple_files = true; | ||||||
|  |  | ||||||
|  | import "common/serial/typed_message.proto"; | ||||||
|  |  | ||||||
|  | // Config is the settings for Commander. | ||||||
|  | message Config { | ||||||
|  |   // Tag of the outbound handler that handles grpc connections. | ||||||
|  |   string tag = 1; | ||||||
|  |   // Services that supported by this server. All services must implement Service | ||||||
|  |   // interface. | ||||||
|  |   repeated xray.common.serial.TypedMessage service = 2; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ReflectionConfig is the placeholder config for ReflectionService. | ||||||
|  | message ReflectionConfig {} | ||||||
							
								
								
									
										9
									
								
								app/commander/errors.generated.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								app/commander/errors.generated.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | package commander | ||||||
|  |  | ||||||
|  | import "github.com/xtls/xray-core/v1/common/errors" | ||||||
|  |  | ||||||
|  | type errPathObjHolder struct{} | ||||||
|  |  | ||||||
|  | func newError(values ...interface{}) *errors.Error { | ||||||
|  | 	return errors.New(values...).WithPathObj(errPathObjHolder{}) | ||||||
|  | } | ||||||
							
								
								
									
										110
									
								
								app/commander/outbound.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								app/commander/outbound.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,110 @@ | |||||||
|  | // +build !confonly | ||||||
|  |  | ||||||
|  | package commander | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"sync" | ||||||
|  |  | ||||||
|  | 	"github.com/xtls/xray-core/v1/common" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/net" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/signal/done" | ||||||
|  | 	"github.com/xtls/xray-core/v1/transport" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // OutboundListener is a net.Listener for listening gRPC connections. | ||||||
|  | type OutboundListener struct { | ||||||
|  | 	buffer chan net.Conn | ||||||
|  | 	done   *done.Instance | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (l *OutboundListener) add(conn net.Conn) { | ||||||
|  | 	select { | ||||||
|  | 	case l.buffer <- conn: | ||||||
|  | 	case <-l.done.Wait(): | ||||||
|  | 		conn.Close() | ||||||
|  | 	default: | ||||||
|  | 		conn.Close() | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Accept implements net.Listener. | ||||||
|  | func (l *OutboundListener) Accept() (net.Conn, error) { | ||||||
|  | 	select { | ||||||
|  | 	case <-l.done.Wait(): | ||||||
|  | 		return nil, newError("listen closed") | ||||||
|  | 	case c := <-l.buffer: | ||||||
|  | 		return c, nil | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Close implement net.Listener. | ||||||
|  | func (l *OutboundListener) Close() error { | ||||||
|  | 	common.Must(l.done.Close()) | ||||||
|  | L: | ||||||
|  | 	for { | ||||||
|  | 		select { | ||||||
|  | 		case c := <-l.buffer: | ||||||
|  | 			c.Close() | ||||||
|  | 		default: | ||||||
|  | 			break L | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Addr implements net.Listener. | ||||||
|  | func (l *OutboundListener) Addr() net.Addr { | ||||||
|  | 	return &net.TCPAddr{ | ||||||
|  | 		IP:   net.IP{0, 0, 0, 0}, | ||||||
|  | 		Port: 0, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Outbound is a outbound.Handler that handles gRPC connections. | ||||||
|  | type Outbound struct { | ||||||
|  | 	tag      string | ||||||
|  | 	listener *OutboundListener | ||||||
|  | 	access   sync.RWMutex | ||||||
|  | 	closed   bool | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Dispatch implements outbound.Handler. | ||||||
|  | func (co *Outbound) Dispatch(ctx context.Context, link *transport.Link) { | ||||||
|  | 	co.access.RLock() | ||||||
|  |  | ||||||
|  | 	if co.closed { | ||||||
|  | 		common.Interrupt(link.Reader) | ||||||
|  | 		common.Interrupt(link.Writer) | ||||||
|  | 		co.access.RUnlock() | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	closeSignal := done.New() | ||||||
|  | 	c := net.NewConnection(net.ConnectionInputMulti(link.Writer), net.ConnectionOutputMulti(link.Reader), net.ConnectionOnClose(closeSignal)) | ||||||
|  | 	co.listener.add(c) | ||||||
|  | 	co.access.RUnlock() | ||||||
|  | 	<-closeSignal.Wait() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Tag implements outbound.Handler. | ||||||
|  | func (co *Outbound) Tag() string { | ||||||
|  | 	return co.tag | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Start implements common.Runnable. | ||||||
|  | func (co *Outbound) Start() error { | ||||||
|  | 	co.access.Lock() | ||||||
|  | 	co.closed = false | ||||||
|  | 	co.access.Unlock() | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Close implements common.Closable. | ||||||
|  | func (co *Outbound) Close() error { | ||||||
|  | 	co.access.Lock() | ||||||
|  | 	defer co.access.Unlock() | ||||||
|  |  | ||||||
|  | 	co.closed = true | ||||||
|  | 	return co.listener.Close() | ||||||
|  | } | ||||||
							
								
								
									
										29
									
								
								app/commander/service.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								app/commander/service.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | |||||||
|  | // +build !confonly | ||||||
|  |  | ||||||
|  | package commander | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  |  | ||||||
|  | 	"github.com/xtls/xray-core/v1/common" | ||||||
|  | 	"google.golang.org/grpc" | ||||||
|  | 	"google.golang.org/grpc/reflection" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Service is a Commander service. | ||||||
|  | type Service interface { | ||||||
|  | 	// Register registers the service itself to a gRPC server. | ||||||
|  | 	Register(*grpc.Server) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type reflectionService struct{} | ||||||
|  |  | ||||||
|  | func (r reflectionService) Register(s *grpc.Server) { | ||||||
|  | 	reflection.Register(s) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func init() { | ||||||
|  | 	common.Must(common.RegisterConfig((*ReflectionConfig)(nil), func(ctx context.Context, cfg interface{}) (interface{}, error) { | ||||||
|  | 		return reflectionService{}, nil | ||||||
|  | 	})) | ||||||
|  | } | ||||||
							
								
								
									
										209
									
								
								app/dispatcher/config.pb.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										209
									
								
								app/dispatcher/config.pb.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,209 @@ | |||||||
|  | // Code generated by protoc-gen-go. DO NOT EDIT. | ||||||
|  | // versions: | ||||||
|  | // 	protoc-gen-go v1.25.0 | ||||||
|  | // 	protoc        v3.14.0 | ||||||
|  | // source: app/dispatcher/config.proto | ||||||
|  |  | ||||||
|  | package dispatcher | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	proto "github.com/golang/protobuf/proto" | ||||||
|  | 	protoreflect "google.golang.org/protobuf/reflect/protoreflect" | ||||||
|  | 	protoimpl "google.golang.org/protobuf/runtime/protoimpl" | ||||||
|  | 	reflect "reflect" | ||||||
|  | 	sync "sync" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	// Verify that this generated code is sufficiently up-to-date. | ||||||
|  | 	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) | ||||||
|  | 	// Verify that runtime/protoimpl is sufficiently up-to-date. | ||||||
|  | 	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // This is a compile-time assertion that a sufficiently up-to-date version | ||||||
|  | // of the legacy proto package is being used. | ||||||
|  | const _ = proto.ProtoPackageIsVersion4 | ||||||
|  |  | ||||||
|  | type SessionConfig struct { | ||||||
|  | 	state         protoimpl.MessageState | ||||||
|  | 	sizeCache     protoimpl.SizeCache | ||||||
|  | 	unknownFields protoimpl.UnknownFields | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *SessionConfig) Reset() { | ||||||
|  | 	*x = SessionConfig{} | ||||||
|  | 	if protoimpl.UnsafeEnabled { | ||||||
|  | 		mi := &file_app_dispatcher_config_proto_msgTypes[0] | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		ms.StoreMessageInfo(mi) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *SessionConfig) String() string { | ||||||
|  | 	return protoimpl.X.MessageStringOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (*SessionConfig) ProtoMessage() {} | ||||||
|  |  | ||||||
|  | func (x *SessionConfig) ProtoReflect() protoreflect.Message { | ||||||
|  | 	mi := &file_app_dispatcher_config_proto_msgTypes[0] | ||||||
|  | 	if protoimpl.UnsafeEnabled && x != nil { | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		if ms.LoadMessageInfo() == nil { | ||||||
|  | 			ms.StoreMessageInfo(mi) | ||||||
|  | 		} | ||||||
|  | 		return ms | ||||||
|  | 	} | ||||||
|  | 	return mi.MessageOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Deprecated: Use SessionConfig.ProtoReflect.Descriptor instead. | ||||||
|  | func (*SessionConfig) Descriptor() ([]byte, []int) { | ||||||
|  | 	return file_app_dispatcher_config_proto_rawDescGZIP(), []int{0} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Config struct { | ||||||
|  | 	state         protoimpl.MessageState | ||||||
|  | 	sizeCache     protoimpl.SizeCache | ||||||
|  | 	unknownFields protoimpl.UnknownFields | ||||||
|  |  | ||||||
|  | 	Settings *SessionConfig `protobuf:"bytes,1,opt,name=settings,proto3" json:"settings,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Config) Reset() { | ||||||
|  | 	*x = Config{} | ||||||
|  | 	if protoimpl.UnsafeEnabled { | ||||||
|  | 		mi := &file_app_dispatcher_config_proto_msgTypes[1] | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		ms.StoreMessageInfo(mi) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Config) String() string { | ||||||
|  | 	return protoimpl.X.MessageStringOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (*Config) ProtoMessage() {} | ||||||
|  |  | ||||||
|  | func (x *Config) ProtoReflect() protoreflect.Message { | ||||||
|  | 	mi := &file_app_dispatcher_config_proto_msgTypes[1] | ||||||
|  | 	if protoimpl.UnsafeEnabled && x != nil { | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		if ms.LoadMessageInfo() == nil { | ||||||
|  | 			ms.StoreMessageInfo(mi) | ||||||
|  | 		} | ||||||
|  | 		return ms | ||||||
|  | 	} | ||||||
|  | 	return mi.MessageOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Deprecated: Use Config.ProtoReflect.Descriptor instead. | ||||||
|  | func (*Config) Descriptor() ([]byte, []int) { | ||||||
|  | 	return file_app_dispatcher_config_proto_rawDescGZIP(), []int{1} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Config) GetSettings() *SessionConfig { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Settings | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var File_app_dispatcher_config_proto protoreflect.FileDescriptor | ||||||
|  |  | ||||||
|  | var file_app_dispatcher_config_proto_rawDesc = []byte{ | ||||||
|  | 	0x0a, 0x1b, 0x61, 0x70, 0x70, 0x2f, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, | ||||||
|  | 	0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x13, 0x78, | ||||||
|  | 	0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, | ||||||
|  | 	0x65, 0x72, 0x22, 0x15, 0x0a, 0x0d, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, | ||||||
|  | 	0x66, 0x69, 0x67, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x22, 0x48, 0x0a, 0x06, 0x43, 0x6f, 0x6e, | ||||||
|  | 	0x66, 0x69, 0x67, 0x12, 0x3e, 0x0a, 0x08, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, | ||||||
|  | 	0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, | ||||||
|  | 	0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x53, 0x65, 0x73, 0x73, | ||||||
|  | 	0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x08, 0x73, 0x65, 0x74, 0x74, 0x69, | ||||||
|  | 	0x6e, 0x67, 0x73, 0x42, 0x5e, 0x0a, 0x17, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, | ||||||
|  | 	0x61, 0x70, 0x70, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x50, 0x01, | ||||||
|  | 	0x5a, 0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, | ||||||
|  | 	0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x61, | ||||||
|  | 	0x70, 0x70, 0x2f, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0xaa, 0x02, 0x13, | ||||||
|  | 	0x58, 0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, | ||||||
|  | 	0x68, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var ( | ||||||
|  | 	file_app_dispatcher_config_proto_rawDescOnce sync.Once | ||||||
|  | 	file_app_dispatcher_config_proto_rawDescData = file_app_dispatcher_config_proto_rawDesc | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func file_app_dispatcher_config_proto_rawDescGZIP() []byte { | ||||||
|  | 	file_app_dispatcher_config_proto_rawDescOnce.Do(func() { | ||||||
|  | 		file_app_dispatcher_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_app_dispatcher_config_proto_rawDescData) | ||||||
|  | 	}) | ||||||
|  | 	return file_app_dispatcher_config_proto_rawDescData | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var file_app_dispatcher_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2) | ||||||
|  | var file_app_dispatcher_config_proto_goTypes = []interface{}{ | ||||||
|  | 	(*SessionConfig)(nil), // 0: xray.app.dispatcher.SessionConfig | ||||||
|  | 	(*Config)(nil),        // 1: xray.app.dispatcher.Config | ||||||
|  | } | ||||||
|  | var file_app_dispatcher_config_proto_depIdxs = []int32{ | ||||||
|  | 	0, // 0: xray.app.dispatcher.Config.settings:type_name -> xray.app.dispatcher.SessionConfig | ||||||
|  | 	1, // [1:1] is the sub-list for method output_type | ||||||
|  | 	1, // [1:1] is the sub-list for method input_type | ||||||
|  | 	1, // [1:1] is the sub-list for extension type_name | ||||||
|  | 	1, // [1:1] is the sub-list for extension extendee | ||||||
|  | 	0, // [0:1] is the sub-list for field type_name | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func init() { file_app_dispatcher_config_proto_init() } | ||||||
|  | func file_app_dispatcher_config_proto_init() { | ||||||
|  | 	if File_app_dispatcher_config_proto != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if !protoimpl.UnsafeEnabled { | ||||||
|  | 		file_app_dispatcher_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { | ||||||
|  | 			switch v := v.(*SessionConfig); i { | ||||||
|  | 			case 0: | ||||||
|  | 				return &v.state | ||||||
|  | 			case 1: | ||||||
|  | 				return &v.sizeCache | ||||||
|  | 			case 2: | ||||||
|  | 				return &v.unknownFields | ||||||
|  | 			default: | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		file_app_dispatcher_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { | ||||||
|  | 			switch v := v.(*Config); i { | ||||||
|  | 			case 0: | ||||||
|  | 				return &v.state | ||||||
|  | 			case 1: | ||||||
|  | 				return &v.sizeCache | ||||||
|  | 			case 2: | ||||||
|  | 				return &v.unknownFields | ||||||
|  | 			default: | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	type x struct{} | ||||||
|  | 	out := protoimpl.TypeBuilder{ | ||||||
|  | 		File: protoimpl.DescBuilder{ | ||||||
|  | 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(), | ||||||
|  | 			RawDescriptor: file_app_dispatcher_config_proto_rawDesc, | ||||||
|  | 			NumEnums:      0, | ||||||
|  | 			NumMessages:   2, | ||||||
|  | 			NumExtensions: 0, | ||||||
|  | 			NumServices:   0, | ||||||
|  | 		}, | ||||||
|  | 		GoTypes:           file_app_dispatcher_config_proto_goTypes, | ||||||
|  | 		DependencyIndexes: file_app_dispatcher_config_proto_depIdxs, | ||||||
|  | 		MessageInfos:      file_app_dispatcher_config_proto_msgTypes, | ||||||
|  | 	}.Build() | ||||||
|  | 	File_app_dispatcher_config_proto = out.File | ||||||
|  | 	file_app_dispatcher_config_proto_rawDesc = nil | ||||||
|  | 	file_app_dispatcher_config_proto_goTypes = nil | ||||||
|  | 	file_app_dispatcher_config_proto_depIdxs = nil | ||||||
|  | } | ||||||
							
								
								
									
										15
									
								
								app/dispatcher/config.proto
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								app/dispatcher/config.proto
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | syntax = "proto3"; | ||||||
|  |  | ||||||
|  | package xray.app.dispatcher; | ||||||
|  | option csharp_namespace = "Xray.App.Dispatcher"; | ||||||
|  | option go_package = "github.com/xtls/xray-core/v1/app/dispatcher"; | ||||||
|  | option java_package = "com.xray.app.dispatcher"; | ||||||
|  | option java_multiple_files = true; | ||||||
|  |  | ||||||
|  | message SessionConfig { | ||||||
|  |   reserved 1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message Config { | ||||||
|  |   SessionConfig settings = 1; | ||||||
|  | } | ||||||
							
								
								
									
										301
									
								
								app/dispatcher/default.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										301
									
								
								app/dispatcher/default.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,301 @@ | |||||||
|  | // +build !confonly | ||||||
|  |  | ||||||
|  | package dispatcher | ||||||
|  |  | ||||||
|  | //go:generate go run github.com/xtls/xray-core/v1/common/errors/errorgen | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"strings" | ||||||
|  | 	"sync" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/xtls/xray-core/v1/common" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/buf" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/log" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/net" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/protocol" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/session" | ||||||
|  | 	"github.com/xtls/xray-core/v1/core" | ||||||
|  | 	"github.com/xtls/xray-core/v1/features/outbound" | ||||||
|  | 	"github.com/xtls/xray-core/v1/features/policy" | ||||||
|  | 	"github.com/xtls/xray-core/v1/features/routing" | ||||||
|  | 	routing_session "github.com/xtls/xray-core/v1/features/routing/session" | ||||||
|  | 	"github.com/xtls/xray-core/v1/features/stats" | ||||||
|  | 	"github.com/xtls/xray-core/v1/transport" | ||||||
|  | 	"github.com/xtls/xray-core/v1/transport/pipe" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var ( | ||||||
|  | 	errSniffingTimeout = newError("timeout on sniffing") | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type cachedReader struct { | ||||||
|  | 	sync.Mutex | ||||||
|  | 	reader *pipe.Reader | ||||||
|  | 	cache  buf.MultiBuffer | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r *cachedReader) Cache(b *buf.Buffer) { | ||||||
|  | 	mb, _ := r.reader.ReadMultiBufferTimeout(time.Millisecond * 100) | ||||||
|  | 	r.Lock() | ||||||
|  | 	if !mb.IsEmpty() { | ||||||
|  | 		r.cache, _ = buf.MergeMulti(r.cache, mb) | ||||||
|  | 	} | ||||||
|  | 	b.Clear() | ||||||
|  | 	rawBytes := b.Extend(buf.Size) | ||||||
|  | 	n := r.cache.Copy(rawBytes) | ||||||
|  | 	b.Resize(0, int32(n)) | ||||||
|  | 	r.Unlock() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r *cachedReader) readInternal() buf.MultiBuffer { | ||||||
|  | 	r.Lock() | ||||||
|  | 	defer r.Unlock() | ||||||
|  |  | ||||||
|  | 	if r.cache != nil && !r.cache.IsEmpty() { | ||||||
|  | 		mb := r.cache | ||||||
|  | 		r.cache = nil | ||||||
|  | 		return mb | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r *cachedReader) ReadMultiBuffer() (buf.MultiBuffer, error) { | ||||||
|  | 	mb := r.readInternal() | ||||||
|  | 	if mb != nil { | ||||||
|  | 		return mb, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return r.reader.ReadMultiBuffer() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r *cachedReader) ReadMultiBufferTimeout(timeout time.Duration) (buf.MultiBuffer, error) { | ||||||
|  | 	mb := r.readInternal() | ||||||
|  | 	if mb != nil { | ||||||
|  | 		return mb, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return r.reader.ReadMultiBufferTimeout(timeout) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r *cachedReader) Interrupt() { | ||||||
|  | 	r.Lock() | ||||||
|  | 	if r.cache != nil { | ||||||
|  | 		r.cache = buf.ReleaseMulti(r.cache) | ||||||
|  | 	} | ||||||
|  | 	r.Unlock() | ||||||
|  | 	r.reader.Interrupt() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // DefaultDispatcher is a default implementation of Dispatcher. | ||||||
|  | type DefaultDispatcher struct { | ||||||
|  | 	ohm    outbound.Manager | ||||||
|  | 	router routing.Router | ||||||
|  | 	policy policy.Manager | ||||||
|  | 	stats  stats.Manager | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func init() { | ||||||
|  | 	common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { | ||||||
|  | 		d := new(DefaultDispatcher) | ||||||
|  | 		if err := core.RequireFeatures(ctx, func(om outbound.Manager, router routing.Router, pm policy.Manager, sm stats.Manager) error { | ||||||
|  | 			return d.Init(config.(*Config), om, router, pm, sm) | ||||||
|  | 		}); err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		return d, nil | ||||||
|  | 	})) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Init initializes DefaultDispatcher. | ||||||
|  | func (d *DefaultDispatcher) Init(config *Config, om outbound.Manager, router routing.Router, pm policy.Manager, sm stats.Manager) error { | ||||||
|  | 	d.ohm = om | ||||||
|  | 	d.router = router | ||||||
|  | 	d.policy = pm | ||||||
|  | 	d.stats = sm | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Type implements common.HasType. | ||||||
|  | func (*DefaultDispatcher) Type() interface{} { | ||||||
|  | 	return routing.DispatcherType() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Start implements common.Runnable. | ||||||
|  | func (*DefaultDispatcher) Start() error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Close implements common.Closable. | ||||||
|  | func (*DefaultDispatcher) Close() error { return nil } | ||||||
|  |  | ||||||
|  | func (d *DefaultDispatcher) getLink(ctx context.Context) (*transport.Link, *transport.Link) { | ||||||
|  | 	opt := pipe.OptionsFromContext(ctx) | ||||||
|  | 	uplinkReader, uplinkWriter := pipe.New(opt...) | ||||||
|  | 	downlinkReader, downlinkWriter := pipe.New(opt...) | ||||||
|  |  | ||||||
|  | 	inboundLink := &transport.Link{ | ||||||
|  | 		Reader: downlinkReader, | ||||||
|  | 		Writer: uplinkWriter, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	outboundLink := &transport.Link{ | ||||||
|  | 		Reader: uplinkReader, | ||||||
|  | 		Writer: downlinkWriter, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	sessionInbound := session.InboundFromContext(ctx) | ||||||
|  | 	var user *protocol.MemoryUser | ||||||
|  | 	if sessionInbound != nil { | ||||||
|  | 		user = sessionInbound.User | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if user != nil && len(user.Email) > 0 { | ||||||
|  | 		p := d.policy.ForLevel(user.Level) | ||||||
|  | 		if p.Stats.UserUplink { | ||||||
|  | 			name := "user>>>" + user.Email + ">>>traffic>>>uplink" | ||||||
|  | 			if c, _ := stats.GetOrRegisterCounter(d.stats, name); c != nil { | ||||||
|  | 				inboundLink.Writer = &SizeStatWriter{ | ||||||
|  | 					Counter: c, | ||||||
|  | 					Writer:  inboundLink.Writer, | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		if p.Stats.UserDownlink { | ||||||
|  | 			name := "user>>>" + user.Email + ">>>traffic>>>downlink" | ||||||
|  | 			if c, _ := stats.GetOrRegisterCounter(d.stats, name); c != nil { | ||||||
|  | 				outboundLink.Writer = &SizeStatWriter{ | ||||||
|  | 					Counter: c, | ||||||
|  | 					Writer:  outboundLink.Writer, | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return inboundLink, outboundLink | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func shouldOverride(result SniffResult, domainOverride []string) bool { | ||||||
|  | 	for _, p := range domainOverride { | ||||||
|  | 		if strings.HasPrefix(result.Protocol(), p) { | ||||||
|  | 			return true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Dispatch implements routing.Dispatcher. | ||||||
|  | func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destination) (*transport.Link, error) { | ||||||
|  | 	if !destination.IsValid() { | ||||||
|  | 		panic("Dispatcher: Invalid destination.") | ||||||
|  | 	} | ||||||
|  | 	ob := &session.Outbound{ | ||||||
|  | 		Target: destination, | ||||||
|  | 	} | ||||||
|  | 	ctx = session.ContextWithOutbound(ctx, ob) | ||||||
|  |  | ||||||
|  | 	inbound, outbound := d.getLink(ctx) | ||||||
|  | 	content := session.ContentFromContext(ctx) | ||||||
|  | 	if content == nil { | ||||||
|  | 		content = new(session.Content) | ||||||
|  | 		ctx = session.ContextWithContent(ctx, content) | ||||||
|  | 	} | ||||||
|  | 	sniffingRequest := content.SniffingRequest | ||||||
|  | 	if destination.Network != net.Network_TCP || !sniffingRequest.Enabled { | ||||||
|  | 		go d.routedDispatch(ctx, outbound, destination) | ||||||
|  | 	} else { | ||||||
|  | 		go func() { | ||||||
|  | 			cReader := &cachedReader{ | ||||||
|  | 				reader: outbound.Reader.(*pipe.Reader), | ||||||
|  | 			} | ||||||
|  | 			outbound.Reader = cReader | ||||||
|  | 			result, err := sniffer(ctx, cReader) | ||||||
|  | 			if err == nil { | ||||||
|  | 				content.Protocol = result.Protocol() | ||||||
|  | 			} | ||||||
|  | 			if err == nil && shouldOverride(result, sniffingRequest.OverrideDestinationForProtocol) { | ||||||
|  | 				domain := result.Domain() | ||||||
|  | 				newError("sniffed domain: ", domain).WriteToLog(session.ExportIDToError(ctx)) | ||||||
|  | 				destination.Address = net.ParseAddress(domain) | ||||||
|  | 				ob.Target = destination | ||||||
|  | 			} | ||||||
|  | 			d.routedDispatch(ctx, outbound, destination) | ||||||
|  | 		}() | ||||||
|  | 	} | ||||||
|  | 	return inbound, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func sniffer(ctx context.Context, cReader *cachedReader) (SniffResult, error) { | ||||||
|  | 	payload := buf.New() | ||||||
|  | 	defer payload.Release() | ||||||
|  |  | ||||||
|  | 	sniffer := NewSniffer() | ||||||
|  | 	totalAttempt := 0 | ||||||
|  | 	for { | ||||||
|  | 		select { | ||||||
|  | 		case <-ctx.Done(): | ||||||
|  | 			return nil, ctx.Err() | ||||||
|  | 		default: | ||||||
|  | 			totalAttempt++ | ||||||
|  | 			if totalAttempt > 2 { | ||||||
|  | 				return nil, errSniffingTimeout | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			cReader.Cache(payload) | ||||||
|  | 			if !payload.IsEmpty() { | ||||||
|  | 				result, err := sniffer.Sniff(payload.Bytes()) | ||||||
|  | 				if err != common.ErrNoClue { | ||||||
|  | 					return result, err | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			if payload.IsFull() { | ||||||
|  | 				return nil, errUnknownContent | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.Link, destination net.Destination) { | ||||||
|  | 	var handler outbound.Handler | ||||||
|  |  | ||||||
|  | 	skipRoutePick := false | ||||||
|  | 	if content := session.ContentFromContext(ctx); content != nil { | ||||||
|  | 		skipRoutePick = content.SkipRoutePick | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if d.router != nil && !skipRoutePick { | ||||||
|  | 		if route, err := d.router.PickRoute(routing_session.AsRoutingContext(ctx)); err == nil { | ||||||
|  | 			tag := route.GetOutboundTag() | ||||||
|  | 			if h := d.ohm.GetHandler(tag); h != nil { | ||||||
|  | 				newError("taking detour [", tag, "] for [", destination, "]").WriteToLog(session.ExportIDToError(ctx)) | ||||||
|  | 				handler = h | ||||||
|  | 			} else { | ||||||
|  | 				newError("non existing tag: ", tag).AtWarning().WriteToLog(session.ExportIDToError(ctx)) | ||||||
|  | 			} | ||||||
|  | 		} else { | ||||||
|  | 			newError("default route for ", destination).WriteToLog(session.ExportIDToError(ctx)) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if handler == nil { | ||||||
|  | 		handler = d.ohm.GetDefaultHandler() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if handler == nil { | ||||||
|  | 		newError("default outbound handler not exist").WriteToLog(session.ExportIDToError(ctx)) | ||||||
|  | 		common.Close(link.Writer) | ||||||
|  | 		common.Interrupt(link.Reader) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if accessMessage := log.AccessMessageFromContext(ctx); accessMessage != nil { | ||||||
|  | 		if tag := handler.Tag(); tag != "" { | ||||||
|  | 			accessMessage.Detour = tag | ||||||
|  | 		} | ||||||
|  | 		log.Record(accessMessage) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	handler.Dispatch(ctx, link) | ||||||
|  | } | ||||||
							
								
								
									
										5
									
								
								app/dispatcher/dispatcher.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								app/dispatcher/dispatcher.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | // +build !confonly | ||||||
|  |  | ||||||
|  | package dispatcher | ||||||
|  |  | ||||||
|  | //go:generate go run github.com/xtls/xray-core/v1/common/errors/errorgen | ||||||
							
								
								
									
										9
									
								
								app/dispatcher/errors.generated.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								app/dispatcher/errors.generated.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | package dispatcher | ||||||
|  |  | ||||||
|  | import "github.com/xtls/xray-core/v1/common/errors" | ||||||
|  |  | ||||||
|  | type errPathObjHolder struct{} | ||||||
|  |  | ||||||
|  | func newError(values ...interface{}) *errors.Error { | ||||||
|  | 	return errors.New(values...).WithPathObj(errPathObjHolder{}) | ||||||
|  | } | ||||||
							
								
								
									
										55
									
								
								app/dispatcher/sniffer.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								app/dispatcher/sniffer.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | |||||||
|  | // +build !confonly | ||||||
|  |  | ||||||
|  | package dispatcher | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"github.com/xtls/xray-core/v1/common" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/protocol/bittorrent" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/protocol/http" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/protocol/tls" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type SniffResult interface { | ||||||
|  | 	Protocol() string | ||||||
|  | 	Domain() string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type protocolSniffer func([]byte) (SniffResult, error) | ||||||
|  |  | ||||||
|  | type Sniffer struct { | ||||||
|  | 	sniffer []protocolSniffer | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewSniffer() *Sniffer { | ||||||
|  | 	return &Sniffer{ | ||||||
|  | 		sniffer: []protocolSniffer{ | ||||||
|  | 			func(b []byte) (SniffResult, error) { return http.SniffHTTP(b) }, | ||||||
|  | 			func(b []byte) (SniffResult, error) { return tls.SniffTLS(b) }, | ||||||
|  | 			func(b []byte) (SniffResult, error) { return bittorrent.SniffBittorrent(b) }, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var errUnknownContent = newError("unknown content") | ||||||
|  |  | ||||||
|  | func (s *Sniffer) Sniff(payload []byte) (SniffResult, error) { | ||||||
|  | 	var pendingSniffer []protocolSniffer | ||||||
|  | 	for _, s := range s.sniffer { | ||||||
|  | 		result, err := s(payload) | ||||||
|  | 		if err == common.ErrNoClue { | ||||||
|  | 			pendingSniffer = append(pendingSniffer, s) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if err == nil && result != nil { | ||||||
|  | 			return result, nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(pendingSniffer) > 0 { | ||||||
|  | 		s.sniffer = pendingSniffer | ||||||
|  | 		return nil, common.ErrNoClue | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil, errUnknownContent | ||||||
|  | } | ||||||
							
								
								
									
										27
									
								
								app/dispatcher/stats.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								app/dispatcher/stats.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | // +build !confonly | ||||||
|  |  | ||||||
|  | package dispatcher | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"github.com/xtls/xray-core/v1/common" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/buf" | ||||||
|  | 	"github.com/xtls/xray-core/v1/features/stats" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type SizeStatWriter struct { | ||||||
|  | 	Counter stats.Counter | ||||||
|  | 	Writer  buf.Writer | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (w *SizeStatWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { | ||||||
|  | 	w.Counter.Add(int64(mb.Len())) | ||||||
|  | 	return w.Writer.WriteMultiBuffer(mb) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (w *SizeStatWriter) Close() error { | ||||||
|  | 	return common.Close(w.Writer) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (w *SizeStatWriter) Interrupt() { | ||||||
|  | 	common.Interrupt(w.Writer) | ||||||
|  | } | ||||||
							
								
								
									
										44
									
								
								app/dispatcher/stats_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								app/dispatcher/stats_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | |||||||
|  | package dispatcher_test | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	. "github.com/xtls/xray-core/v1/app/dispatcher" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/buf" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type TestCounter int64 | ||||||
|  |  | ||||||
|  | func (c *TestCounter) Value() int64 { | ||||||
|  | 	return int64(*c) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *TestCounter) Add(v int64) int64 { | ||||||
|  | 	x := int64(*c) + v | ||||||
|  | 	*c = TestCounter(x) | ||||||
|  | 	return x | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *TestCounter) Set(v int64) int64 { | ||||||
|  | 	*c = TestCounter(v) | ||||||
|  | 	return v | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestStatsWriter(t *testing.T) { | ||||||
|  | 	var c TestCounter | ||||||
|  | 	writer := &SizeStatWriter{ | ||||||
|  | 		Counter: &c, | ||||||
|  | 		Writer:  buf.Discard, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	mb := buf.MergeBytes(nil, []byte("abcd")) | ||||||
|  | 	common.Must(writer.WriteMultiBuffer(mb)) | ||||||
|  |  | ||||||
|  | 	mb = buf.MergeBytes(nil, []byte("efg")) | ||||||
|  | 	common.Must(writer.WriteMultiBuffer(mb)) | ||||||
|  |  | ||||||
|  | 	if c.Value() != 7 { | ||||||
|  | 		t.Fatal("unexpected counter value. want 7, but got ", c.Value()) | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										654
									
								
								app/dns/config.pb.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										654
									
								
								app/dns/config.pb.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,654 @@ | |||||||
|  | // Code generated by protoc-gen-go. DO NOT EDIT. | ||||||
|  | // versions: | ||||||
|  | // 	protoc-gen-go v1.25.0 | ||||||
|  | // 	protoc        v3.14.0 | ||||||
|  | // source: app/dns/config.proto | ||||||
|  |  | ||||||
|  | package dns | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	proto "github.com/golang/protobuf/proto" | ||||||
|  | 	router "github.com/xtls/xray-core/v1/app/router" | ||||||
|  | 	net "github.com/xtls/xray-core/v1/common/net" | ||||||
|  | 	protoreflect "google.golang.org/protobuf/reflect/protoreflect" | ||||||
|  | 	protoimpl "google.golang.org/protobuf/runtime/protoimpl" | ||||||
|  | 	reflect "reflect" | ||||||
|  | 	sync "sync" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	// Verify that this generated code is sufficiently up-to-date. | ||||||
|  | 	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) | ||||||
|  | 	// Verify that runtime/protoimpl is sufficiently up-to-date. | ||||||
|  | 	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // This is a compile-time assertion that a sufficiently up-to-date version | ||||||
|  | // of the legacy proto package is being used. | ||||||
|  | const _ = proto.ProtoPackageIsVersion4 | ||||||
|  |  | ||||||
|  | type DomainMatchingType int32 | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	DomainMatchingType_Full      DomainMatchingType = 0 | ||||||
|  | 	DomainMatchingType_Subdomain DomainMatchingType = 1 | ||||||
|  | 	DomainMatchingType_Keyword   DomainMatchingType = 2 | ||||||
|  | 	DomainMatchingType_Regex     DomainMatchingType = 3 | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Enum value maps for DomainMatchingType. | ||||||
|  | var ( | ||||||
|  | 	DomainMatchingType_name = map[int32]string{ | ||||||
|  | 		0: "Full", | ||||||
|  | 		1: "Subdomain", | ||||||
|  | 		2: "Keyword", | ||||||
|  | 		3: "Regex", | ||||||
|  | 	} | ||||||
|  | 	DomainMatchingType_value = map[string]int32{ | ||||||
|  | 		"Full":      0, | ||||||
|  | 		"Subdomain": 1, | ||||||
|  | 		"Keyword":   2, | ||||||
|  | 		"Regex":     3, | ||||||
|  | 	} | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func (x DomainMatchingType) Enum() *DomainMatchingType { | ||||||
|  | 	p := new(DomainMatchingType) | ||||||
|  | 	*p = x | ||||||
|  | 	return p | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x DomainMatchingType) String() string { | ||||||
|  | 	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (DomainMatchingType) Descriptor() protoreflect.EnumDescriptor { | ||||||
|  | 	return file_app_dns_config_proto_enumTypes[0].Descriptor() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (DomainMatchingType) Type() protoreflect.EnumType { | ||||||
|  | 	return &file_app_dns_config_proto_enumTypes[0] | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x DomainMatchingType) Number() protoreflect.EnumNumber { | ||||||
|  | 	return protoreflect.EnumNumber(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Deprecated: Use DomainMatchingType.Descriptor instead. | ||||||
|  | func (DomainMatchingType) EnumDescriptor() ([]byte, []int) { | ||||||
|  | 	return file_app_dns_config_proto_rawDescGZIP(), []int{0} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type NameServer struct { | ||||||
|  | 	state         protoimpl.MessageState | ||||||
|  | 	sizeCache     protoimpl.SizeCache | ||||||
|  | 	unknownFields protoimpl.UnknownFields | ||||||
|  |  | ||||||
|  | 	Address           *net.Endpoint                `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` | ||||||
|  | 	PrioritizedDomain []*NameServer_PriorityDomain `protobuf:"bytes,2,rep,name=prioritized_domain,json=prioritizedDomain,proto3" json:"prioritized_domain,omitempty"` | ||||||
|  | 	Geoip             []*router.GeoIP              `protobuf:"bytes,3,rep,name=geoip,proto3" json:"geoip,omitempty"` | ||||||
|  | 	OriginalRules     []*NameServer_OriginalRule   `protobuf:"bytes,4,rep,name=original_rules,json=originalRules,proto3" json:"original_rules,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *NameServer) Reset() { | ||||||
|  | 	*x = NameServer{} | ||||||
|  | 	if protoimpl.UnsafeEnabled { | ||||||
|  | 		mi := &file_app_dns_config_proto_msgTypes[0] | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		ms.StoreMessageInfo(mi) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *NameServer) String() string { | ||||||
|  | 	return protoimpl.X.MessageStringOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (*NameServer) ProtoMessage() {} | ||||||
|  |  | ||||||
|  | func (x *NameServer) ProtoReflect() protoreflect.Message { | ||||||
|  | 	mi := &file_app_dns_config_proto_msgTypes[0] | ||||||
|  | 	if protoimpl.UnsafeEnabled && x != nil { | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		if ms.LoadMessageInfo() == nil { | ||||||
|  | 			ms.StoreMessageInfo(mi) | ||||||
|  | 		} | ||||||
|  | 		return ms | ||||||
|  | 	} | ||||||
|  | 	return mi.MessageOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Deprecated: Use NameServer.ProtoReflect.Descriptor instead. | ||||||
|  | func (*NameServer) Descriptor() ([]byte, []int) { | ||||||
|  | 	return file_app_dns_config_proto_rawDescGZIP(), []int{0} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *NameServer) GetAddress() *net.Endpoint { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Address | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *NameServer) GetPrioritizedDomain() []*NameServer_PriorityDomain { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.PrioritizedDomain | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *NameServer) GetGeoip() []*router.GeoIP { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Geoip | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *NameServer) GetOriginalRules() []*NameServer_OriginalRule { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.OriginalRules | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Config struct { | ||||||
|  | 	state         protoimpl.MessageState | ||||||
|  | 	sizeCache     protoimpl.SizeCache | ||||||
|  | 	unknownFields protoimpl.UnknownFields | ||||||
|  |  | ||||||
|  | 	// Nameservers used by this DNS. Only traditional UDP servers are support at | ||||||
|  | 	// the moment. A special value 'localhost' as a domain address can be set to | ||||||
|  | 	// use DNS on local system. | ||||||
|  | 	// | ||||||
|  | 	// Deprecated: Do not use. | ||||||
|  | 	NameServers []*net.Endpoint `protobuf:"bytes,1,rep,name=NameServers,proto3" json:"NameServers,omitempty"` | ||||||
|  | 	// NameServer list used by this DNS client. | ||||||
|  | 	NameServer []*NameServer `protobuf:"bytes,5,rep,name=name_server,json=nameServer,proto3" json:"name_server,omitempty"` | ||||||
|  | 	// Static hosts. Domain to IP. | ||||||
|  | 	// Deprecated. Use static_hosts. | ||||||
|  | 	// | ||||||
|  | 	// Deprecated: Do not use. | ||||||
|  | 	Hosts map[string]*net.IPOrDomain `protobuf:"bytes,2,rep,name=Hosts,proto3" json:"Hosts,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` | ||||||
|  | 	// Client IP for EDNS client subnet. Must be 4 bytes (IPv4) or 16 bytes | ||||||
|  | 	// (IPv6). | ||||||
|  | 	ClientIp    []byte                `protobuf:"bytes,3,opt,name=client_ip,json=clientIp,proto3" json:"client_ip,omitempty"` | ||||||
|  | 	StaticHosts []*Config_HostMapping `protobuf:"bytes,4,rep,name=static_hosts,json=staticHosts,proto3" json:"static_hosts,omitempty"` | ||||||
|  | 	// Tag is the inbound tag of DNS client. | ||||||
|  | 	Tag string `protobuf:"bytes,6,opt,name=tag,proto3" json:"tag,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Config) Reset() { | ||||||
|  | 	*x = Config{} | ||||||
|  | 	if protoimpl.UnsafeEnabled { | ||||||
|  | 		mi := &file_app_dns_config_proto_msgTypes[1] | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		ms.StoreMessageInfo(mi) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Config) String() string { | ||||||
|  | 	return protoimpl.X.MessageStringOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (*Config) ProtoMessage() {} | ||||||
|  |  | ||||||
|  | func (x *Config) ProtoReflect() protoreflect.Message { | ||||||
|  | 	mi := &file_app_dns_config_proto_msgTypes[1] | ||||||
|  | 	if protoimpl.UnsafeEnabled && x != nil { | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		if ms.LoadMessageInfo() == nil { | ||||||
|  | 			ms.StoreMessageInfo(mi) | ||||||
|  | 		} | ||||||
|  | 		return ms | ||||||
|  | 	} | ||||||
|  | 	return mi.MessageOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Deprecated: Use Config.ProtoReflect.Descriptor instead. | ||||||
|  | func (*Config) Descriptor() ([]byte, []int) { | ||||||
|  | 	return file_app_dns_config_proto_rawDescGZIP(), []int{1} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Deprecated: Do not use. | ||||||
|  | func (x *Config) GetNameServers() []*net.Endpoint { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.NameServers | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Config) GetNameServer() []*NameServer { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.NameServer | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Deprecated: Do not use. | ||||||
|  | func (x *Config) GetHosts() map[string]*net.IPOrDomain { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Hosts | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Config) GetClientIp() []byte { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.ClientIp | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Config) GetStaticHosts() []*Config_HostMapping { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.StaticHosts | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Config) GetTag() string { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Tag | ||||||
|  | 	} | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type NameServer_PriorityDomain struct { | ||||||
|  | 	state         protoimpl.MessageState | ||||||
|  | 	sizeCache     protoimpl.SizeCache | ||||||
|  | 	unknownFields protoimpl.UnknownFields | ||||||
|  |  | ||||||
|  | 	Type   DomainMatchingType `protobuf:"varint,1,opt,name=type,proto3,enum=xray.app.dns.DomainMatchingType" json:"type,omitempty"` | ||||||
|  | 	Domain string             `protobuf:"bytes,2,opt,name=domain,proto3" json:"domain,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *NameServer_PriorityDomain) Reset() { | ||||||
|  | 	*x = NameServer_PriorityDomain{} | ||||||
|  | 	if protoimpl.UnsafeEnabled { | ||||||
|  | 		mi := &file_app_dns_config_proto_msgTypes[2] | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		ms.StoreMessageInfo(mi) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *NameServer_PriorityDomain) String() string { | ||||||
|  | 	return protoimpl.X.MessageStringOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (*NameServer_PriorityDomain) ProtoMessage() {} | ||||||
|  |  | ||||||
|  | func (x *NameServer_PriorityDomain) ProtoReflect() protoreflect.Message { | ||||||
|  | 	mi := &file_app_dns_config_proto_msgTypes[2] | ||||||
|  | 	if protoimpl.UnsafeEnabled && x != nil { | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		if ms.LoadMessageInfo() == nil { | ||||||
|  | 			ms.StoreMessageInfo(mi) | ||||||
|  | 		} | ||||||
|  | 		return ms | ||||||
|  | 	} | ||||||
|  | 	return mi.MessageOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Deprecated: Use NameServer_PriorityDomain.ProtoReflect.Descriptor instead. | ||||||
|  | func (*NameServer_PriorityDomain) Descriptor() ([]byte, []int) { | ||||||
|  | 	return file_app_dns_config_proto_rawDescGZIP(), []int{0, 0} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *NameServer_PriorityDomain) GetType() DomainMatchingType { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Type | ||||||
|  | 	} | ||||||
|  | 	return DomainMatchingType_Full | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *NameServer_PriorityDomain) GetDomain() string { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Domain | ||||||
|  | 	} | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type NameServer_OriginalRule struct { | ||||||
|  | 	state         protoimpl.MessageState | ||||||
|  | 	sizeCache     protoimpl.SizeCache | ||||||
|  | 	unknownFields protoimpl.UnknownFields | ||||||
|  |  | ||||||
|  | 	Rule string `protobuf:"bytes,1,opt,name=rule,proto3" json:"rule,omitempty"` | ||||||
|  | 	Size uint32 `protobuf:"varint,2,opt,name=size,proto3" json:"size,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *NameServer_OriginalRule) Reset() { | ||||||
|  | 	*x = NameServer_OriginalRule{} | ||||||
|  | 	if protoimpl.UnsafeEnabled { | ||||||
|  | 		mi := &file_app_dns_config_proto_msgTypes[3] | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		ms.StoreMessageInfo(mi) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *NameServer_OriginalRule) String() string { | ||||||
|  | 	return protoimpl.X.MessageStringOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (*NameServer_OriginalRule) ProtoMessage() {} | ||||||
|  |  | ||||||
|  | func (x *NameServer_OriginalRule) ProtoReflect() protoreflect.Message { | ||||||
|  | 	mi := &file_app_dns_config_proto_msgTypes[3] | ||||||
|  | 	if protoimpl.UnsafeEnabled && x != nil { | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		if ms.LoadMessageInfo() == nil { | ||||||
|  | 			ms.StoreMessageInfo(mi) | ||||||
|  | 		} | ||||||
|  | 		return ms | ||||||
|  | 	} | ||||||
|  | 	return mi.MessageOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Deprecated: Use NameServer_OriginalRule.ProtoReflect.Descriptor instead. | ||||||
|  | func (*NameServer_OriginalRule) Descriptor() ([]byte, []int) { | ||||||
|  | 	return file_app_dns_config_proto_rawDescGZIP(), []int{0, 1} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *NameServer_OriginalRule) GetRule() string { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Rule | ||||||
|  | 	} | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *NameServer_OriginalRule) GetSize() uint32 { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Size | ||||||
|  | 	} | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Config_HostMapping struct { | ||||||
|  | 	state         protoimpl.MessageState | ||||||
|  | 	sizeCache     protoimpl.SizeCache | ||||||
|  | 	unknownFields protoimpl.UnknownFields | ||||||
|  |  | ||||||
|  | 	Type   DomainMatchingType `protobuf:"varint,1,opt,name=type,proto3,enum=xray.app.dns.DomainMatchingType" json:"type,omitempty"` | ||||||
|  | 	Domain string             `protobuf:"bytes,2,opt,name=domain,proto3" json:"domain,omitempty"` | ||||||
|  | 	Ip     [][]byte           `protobuf:"bytes,3,rep,name=ip,proto3" json:"ip,omitempty"` | ||||||
|  | 	// ProxiedDomain indicates the mapped domain has the same IP address on this | ||||||
|  | 	// domain. Xray will use this domain for IP queries. This field is only | ||||||
|  | 	// effective if ip is empty. | ||||||
|  | 	ProxiedDomain string `protobuf:"bytes,4,opt,name=proxied_domain,json=proxiedDomain,proto3" json:"proxied_domain,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Config_HostMapping) Reset() { | ||||||
|  | 	*x = Config_HostMapping{} | ||||||
|  | 	if protoimpl.UnsafeEnabled { | ||||||
|  | 		mi := &file_app_dns_config_proto_msgTypes[5] | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		ms.StoreMessageInfo(mi) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Config_HostMapping) String() string { | ||||||
|  | 	return protoimpl.X.MessageStringOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (*Config_HostMapping) ProtoMessage() {} | ||||||
|  |  | ||||||
|  | func (x *Config_HostMapping) ProtoReflect() protoreflect.Message { | ||||||
|  | 	mi := &file_app_dns_config_proto_msgTypes[5] | ||||||
|  | 	if protoimpl.UnsafeEnabled && x != nil { | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		if ms.LoadMessageInfo() == nil { | ||||||
|  | 			ms.StoreMessageInfo(mi) | ||||||
|  | 		} | ||||||
|  | 		return ms | ||||||
|  | 	} | ||||||
|  | 	return mi.MessageOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Deprecated: Use Config_HostMapping.ProtoReflect.Descriptor instead. | ||||||
|  | func (*Config_HostMapping) Descriptor() ([]byte, []int) { | ||||||
|  | 	return file_app_dns_config_proto_rawDescGZIP(), []int{1, 1} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Config_HostMapping) GetType() DomainMatchingType { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Type | ||||||
|  | 	} | ||||||
|  | 	return DomainMatchingType_Full | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Config_HostMapping) GetDomain() string { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Domain | ||||||
|  | 	} | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Config_HostMapping) GetIp() [][]byte { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Ip | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Config_HostMapping) GetProxiedDomain() string { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.ProxiedDomain | ||||||
|  | 	} | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var File_app_dns_config_proto protoreflect.FileDescriptor | ||||||
|  |  | ||||||
|  | var file_app_dns_config_proto_rawDesc = []byte{ | ||||||
|  | 	0x0a, 0x14, 0x61, 0x70, 0x70, 0x2f, 0x64, 0x6e, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, | ||||||
|  | 	0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0c, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, | ||||||
|  | 	0x2e, 0x64, 0x6e, 0x73, 0x1a, 0x18, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, | ||||||
|  | 	0x2f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, | ||||||
|  | 	0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x64, 0x65, 0x73, 0x74, 0x69, | ||||||
|  | 	0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x17, 0x61, 0x70, | ||||||
|  | 	0x70, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, | ||||||
|  | 	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xad, 0x03, 0x0a, 0x0a, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, | ||||||
|  | 	0x72, 0x76, 0x65, 0x72, 0x12, 0x33, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, | ||||||
|  | 	0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, | ||||||
|  | 	0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, | ||||||
|  | 	0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x56, 0x0a, 0x12, 0x70, 0x72, 0x69, | ||||||
|  | 	0x6f, 0x72, 0x69, 0x74, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, | ||||||
|  | 	0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, | ||||||
|  | 	0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, | ||||||
|  | 	0x50, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x11, | ||||||
|  | 	0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x7a, 0x65, 0x64, 0x44, 0x6f, 0x6d, 0x61, 0x69, | ||||||
|  | 	0x6e, 0x12, 0x2c, 0x0a, 0x05, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, | ||||||
|  | 	0x32, 0x16, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, | ||||||
|  | 	0x65, 0x72, 0x2e, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x52, 0x05, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x12, | ||||||
|  | 	0x4c, 0x0a, 0x0e, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x72, 0x75, 0x6c, 0x65, | ||||||
|  | 	0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, | ||||||
|  | 	0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, | ||||||
|  | 	0x72, 0x2e, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x0d, | ||||||
|  | 	0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x1a, 0x5e, 0x0a, | ||||||
|  | 	0x0e, 0x50, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, | ||||||
|  | 	0x34, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, | ||||||
|  | 	0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x44, 0x6f, 0x6d, | ||||||
|  | 	0x61, 0x69, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x54, 0x79, 0x70, 0x65, 0x52, | ||||||
|  | 	0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, | ||||||
|  | 	0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x1a, 0x36, 0x0a, | ||||||
|  | 	0x0c, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x12, 0x0a, | ||||||
|  | 	0x04, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x72, 0x75, 0x6c, | ||||||
|  | 	0x65, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, | ||||||
|  | 	0x04, 0x73, 0x69, 0x7a, 0x65, 0x22, 0x9f, 0x04, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, | ||||||
|  | 	0x12, 0x3f, 0x0a, 0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x18, | ||||||
|  | 	0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, | ||||||
|  | 	0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, | ||||||
|  | 	0x42, 0x02, 0x18, 0x01, 0x52, 0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, | ||||||
|  | 	0x73, 0x12, 0x39, 0x0a, 0x0b, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, | ||||||
|  | 	0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, | ||||||
|  | 	0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, | ||||||
|  | 	0x52, 0x0a, 0x6e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x39, 0x0a, 0x05, | ||||||
|  | 	0x48, 0x6f, 0x73, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x78, 0x72, | ||||||
|  | 	0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, | ||||||
|  | 	0x67, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x42, 0x02, 0x18, 0x01, | ||||||
|  | 	0x52, 0x05, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, | ||||||
|  | 	0x74, 0x5f, 0x69, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, | ||||||
|  | 	0x6e, 0x74, 0x49, 0x70, 0x12, 0x43, 0x0a, 0x0c, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x5f, 0x68, | ||||||
|  | 	0x6f, 0x73, 0x74, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, | ||||||
|  | 	0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, | ||||||
|  | 	0x2e, 0x48, 0x6f, 0x73, 0x74, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x52, 0x0b, 0x73, 0x74, | ||||||
|  | 	0x61, 0x74, 0x69, 0x63, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, | ||||||
|  | 	0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x1a, 0x55, 0x0a, 0x0a, 0x48, | ||||||
|  | 	0x6f, 0x73, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, | ||||||
|  | 	0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x31, 0x0a, 0x05, 0x76, | ||||||
|  | 	0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x78, 0x72, 0x61, | ||||||
|  | 	0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x49, 0x50, 0x4f, | ||||||
|  | 	0x72, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, | ||||||
|  | 	0x38, 0x01, 0x1a, 0x92, 0x01, 0x0a, 0x0b, 0x48, 0x6f, 0x73, 0x74, 0x4d, 0x61, 0x70, 0x70, 0x69, | ||||||
|  | 	0x6e, 0x67, 0x12, 0x34, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, | ||||||
|  | 	0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, | ||||||
|  | 	0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x54, 0x79, | ||||||
|  | 	0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, | ||||||
|  | 	0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, | ||||||
|  | 	0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x70, | ||||||
|  | 	0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x78, 0x69, 0x65, 0x64, 0x5f, 0x64, 0x6f, 0x6d, 0x61, | ||||||
|  | 	0x69, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x78, 0x69, 0x65, | ||||||
|  | 	0x64, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x2a, 0x45, 0x0a, 0x12, 0x44, 0x6f, 0x6d, 0x61, 0x69, | ||||||
|  | 	0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x54, 0x79, 0x70, 0x65, 0x12, 0x08, 0x0a, | ||||||
|  | 	0x04, 0x46, 0x75, 0x6c, 0x6c, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x75, 0x62, 0x64, 0x6f, | ||||||
|  | 	0x6d, 0x61, 0x69, 0x6e, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x4b, 0x65, 0x79, 0x77, 0x6f, 0x72, | ||||||
|  | 	0x64, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x52, 0x65, 0x67, 0x65, 0x78, 0x10, 0x03, 0x42, 0x49, | ||||||
|  | 	0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, | ||||||
|  | 	0x6e, 0x73, 0x50, 0x01, 0x5a, 0x24, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, | ||||||
|  | 	0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, | ||||||
|  | 	0x76, 0x31, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x64, 0x6e, 0x73, 0xaa, 0x02, 0x0c, 0x58, 0x72, 0x61, | ||||||
|  | 	0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x44, 0x6e, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, | ||||||
|  | 	0x33, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var ( | ||||||
|  | 	file_app_dns_config_proto_rawDescOnce sync.Once | ||||||
|  | 	file_app_dns_config_proto_rawDescData = file_app_dns_config_proto_rawDesc | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func file_app_dns_config_proto_rawDescGZIP() []byte { | ||||||
|  | 	file_app_dns_config_proto_rawDescOnce.Do(func() { | ||||||
|  | 		file_app_dns_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_app_dns_config_proto_rawDescData) | ||||||
|  | 	}) | ||||||
|  | 	return file_app_dns_config_proto_rawDescData | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var file_app_dns_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1) | ||||||
|  | var file_app_dns_config_proto_msgTypes = make([]protoimpl.MessageInfo, 6) | ||||||
|  | var file_app_dns_config_proto_goTypes = []interface{}{ | ||||||
|  | 	(DomainMatchingType)(0),           // 0: xray.app.dns.DomainMatchingType | ||||||
|  | 	(*NameServer)(nil),                // 1: xray.app.dns.NameServer | ||||||
|  | 	(*Config)(nil),                    // 2: xray.app.dns.Config | ||||||
|  | 	(*NameServer_PriorityDomain)(nil), // 3: xray.app.dns.NameServer.PriorityDomain | ||||||
|  | 	(*NameServer_OriginalRule)(nil),   // 4: xray.app.dns.NameServer.OriginalRule | ||||||
|  | 	nil,                               // 5: xray.app.dns.Config.HostsEntry | ||||||
|  | 	(*Config_HostMapping)(nil),        // 6: xray.app.dns.Config.HostMapping | ||||||
|  | 	(*net.Endpoint)(nil),              // 7: xray.common.net.Endpoint | ||||||
|  | 	(*router.GeoIP)(nil),              // 8: xray.app.router.GeoIP | ||||||
|  | 	(*net.IPOrDomain)(nil),            // 9: xray.common.net.IPOrDomain | ||||||
|  | } | ||||||
|  | var file_app_dns_config_proto_depIdxs = []int32{ | ||||||
|  | 	7,  // 0: xray.app.dns.NameServer.address:type_name -> xray.common.net.Endpoint | ||||||
|  | 	3,  // 1: xray.app.dns.NameServer.prioritized_domain:type_name -> xray.app.dns.NameServer.PriorityDomain | ||||||
|  | 	8,  // 2: xray.app.dns.NameServer.geoip:type_name -> xray.app.router.GeoIP | ||||||
|  | 	4,  // 3: xray.app.dns.NameServer.original_rules:type_name -> xray.app.dns.NameServer.OriginalRule | ||||||
|  | 	7,  // 4: xray.app.dns.Config.NameServers:type_name -> xray.common.net.Endpoint | ||||||
|  | 	1,  // 5: xray.app.dns.Config.name_server:type_name -> xray.app.dns.NameServer | ||||||
|  | 	5,  // 6: xray.app.dns.Config.Hosts:type_name -> xray.app.dns.Config.HostsEntry | ||||||
|  | 	6,  // 7: xray.app.dns.Config.static_hosts:type_name -> xray.app.dns.Config.HostMapping | ||||||
|  | 	0,  // 8: xray.app.dns.NameServer.PriorityDomain.type:type_name -> xray.app.dns.DomainMatchingType | ||||||
|  | 	9,  // 9: xray.app.dns.Config.HostsEntry.value:type_name -> xray.common.net.IPOrDomain | ||||||
|  | 	0,  // 10: xray.app.dns.Config.HostMapping.type:type_name -> xray.app.dns.DomainMatchingType | ||||||
|  | 	11, // [11:11] is the sub-list for method output_type | ||||||
|  | 	11, // [11:11] is the sub-list for method input_type | ||||||
|  | 	11, // [11:11] is the sub-list for extension type_name | ||||||
|  | 	11, // [11:11] is the sub-list for extension extendee | ||||||
|  | 	0,  // [0:11] is the sub-list for field type_name | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func init() { file_app_dns_config_proto_init() } | ||||||
|  | func file_app_dns_config_proto_init() { | ||||||
|  | 	if File_app_dns_config_proto != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if !protoimpl.UnsafeEnabled { | ||||||
|  | 		file_app_dns_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { | ||||||
|  | 			switch v := v.(*NameServer); i { | ||||||
|  | 			case 0: | ||||||
|  | 				return &v.state | ||||||
|  | 			case 1: | ||||||
|  | 				return &v.sizeCache | ||||||
|  | 			case 2: | ||||||
|  | 				return &v.unknownFields | ||||||
|  | 			default: | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		file_app_dns_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { | ||||||
|  | 			switch v := v.(*Config); i { | ||||||
|  | 			case 0: | ||||||
|  | 				return &v.state | ||||||
|  | 			case 1: | ||||||
|  | 				return &v.sizeCache | ||||||
|  | 			case 2: | ||||||
|  | 				return &v.unknownFields | ||||||
|  | 			default: | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		file_app_dns_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { | ||||||
|  | 			switch v := v.(*NameServer_PriorityDomain); i { | ||||||
|  | 			case 0: | ||||||
|  | 				return &v.state | ||||||
|  | 			case 1: | ||||||
|  | 				return &v.sizeCache | ||||||
|  | 			case 2: | ||||||
|  | 				return &v.unknownFields | ||||||
|  | 			default: | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		file_app_dns_config_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { | ||||||
|  | 			switch v := v.(*NameServer_OriginalRule); i { | ||||||
|  | 			case 0: | ||||||
|  | 				return &v.state | ||||||
|  | 			case 1: | ||||||
|  | 				return &v.sizeCache | ||||||
|  | 			case 2: | ||||||
|  | 				return &v.unknownFields | ||||||
|  | 			default: | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		file_app_dns_config_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { | ||||||
|  | 			switch v := v.(*Config_HostMapping); i { | ||||||
|  | 			case 0: | ||||||
|  | 				return &v.state | ||||||
|  | 			case 1: | ||||||
|  | 				return &v.sizeCache | ||||||
|  | 			case 2: | ||||||
|  | 				return &v.unknownFields | ||||||
|  | 			default: | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	type x struct{} | ||||||
|  | 	out := protoimpl.TypeBuilder{ | ||||||
|  | 		File: protoimpl.DescBuilder{ | ||||||
|  | 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(), | ||||||
|  | 			RawDescriptor: file_app_dns_config_proto_rawDesc, | ||||||
|  | 			NumEnums:      1, | ||||||
|  | 			NumMessages:   6, | ||||||
|  | 			NumExtensions: 0, | ||||||
|  | 			NumServices:   0, | ||||||
|  | 		}, | ||||||
|  | 		GoTypes:           file_app_dns_config_proto_goTypes, | ||||||
|  | 		DependencyIndexes: file_app_dns_config_proto_depIdxs, | ||||||
|  | 		EnumInfos:         file_app_dns_config_proto_enumTypes, | ||||||
|  | 		MessageInfos:      file_app_dns_config_proto_msgTypes, | ||||||
|  | 	}.Build() | ||||||
|  | 	File_app_dns_config_proto = out.File | ||||||
|  | 	file_app_dns_config_proto_rawDesc = nil | ||||||
|  | 	file_app_dns_config_proto_goTypes = nil | ||||||
|  | 	file_app_dns_config_proto_depIdxs = nil | ||||||
|  | } | ||||||
							
								
								
									
										71
									
								
								app/dns/config.proto
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								app/dns/config.proto
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | |||||||
|  | syntax = "proto3"; | ||||||
|  |  | ||||||
|  | package xray.app.dns; | ||||||
|  | option csharp_namespace = "Xray.App.Dns"; | ||||||
|  | option go_package = "github.com/xtls/xray-core/v1/app/dns"; | ||||||
|  | option java_package = "com.xray.app.dns"; | ||||||
|  | option java_multiple_files = true; | ||||||
|  |  | ||||||
|  | import "common/net/address.proto"; | ||||||
|  | import "common/net/destination.proto"; | ||||||
|  | import "app/router/config.proto"; | ||||||
|  |  | ||||||
|  | message NameServer { | ||||||
|  |   xray.common.net.Endpoint address = 1; | ||||||
|  |  | ||||||
|  |   message PriorityDomain { | ||||||
|  |     DomainMatchingType type = 1; | ||||||
|  |     string domain = 2; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   message OriginalRule { | ||||||
|  |     string rule = 1; | ||||||
|  |     uint32 size = 2; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   repeated PriorityDomain prioritized_domain = 2; | ||||||
|  |   repeated xray.app.router.GeoIP geoip = 3; | ||||||
|  |   repeated OriginalRule original_rules = 4; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | enum DomainMatchingType { | ||||||
|  |   Full = 0; | ||||||
|  |   Subdomain = 1; | ||||||
|  |   Keyword = 2; | ||||||
|  |   Regex = 3; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message Config { | ||||||
|  |   // Nameservers used by this DNS. Only traditional UDP servers are support at | ||||||
|  |   // the moment. A special value 'localhost' as a domain address can be set to | ||||||
|  |   // use DNS on local system. | ||||||
|  |   repeated xray.common.net.Endpoint NameServers = 1 [deprecated = true]; | ||||||
|  |  | ||||||
|  |   // NameServer list used by this DNS client. | ||||||
|  |   repeated NameServer name_server = 5; | ||||||
|  |  | ||||||
|  |   // Static hosts. Domain to IP. | ||||||
|  |   // Deprecated. Use static_hosts. | ||||||
|  |   map<string, xray.common.net.IPOrDomain> Hosts = 2 [deprecated = true]; | ||||||
|  |  | ||||||
|  |   // Client IP for EDNS client subnet. Must be 4 bytes (IPv4) or 16 bytes | ||||||
|  |   // (IPv6). | ||||||
|  |   bytes client_ip = 3; | ||||||
|  |  | ||||||
|  |   message HostMapping { | ||||||
|  |     DomainMatchingType type = 1; | ||||||
|  |     string domain = 2; | ||||||
|  |  | ||||||
|  |     repeated bytes ip = 3; | ||||||
|  |  | ||||||
|  |     // ProxiedDomain indicates the mapped domain has the same IP address on this | ||||||
|  |     // domain. Xray will use this domain for IP queries. This field is only | ||||||
|  |     // effective if ip is empty. | ||||||
|  |     string proxied_domain = 4; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   repeated HostMapping static_hosts = 4; | ||||||
|  |  | ||||||
|  |   // Tag is the inbound tag of DNS client. | ||||||
|  |   string tag = 6; | ||||||
|  | } | ||||||
							
								
								
									
										4
									
								
								app/dns/dns.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								app/dns/dns.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | |||||||
|  | // Package dns is an implementation of core.DNS feature. | ||||||
|  | package dns | ||||||
|  |  | ||||||
|  | //go:generate go run github.com/xtls/xray-core/v1/common/errors/errorgen | ||||||
							
								
								
									
										230
									
								
								app/dns/dnscommon.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										230
									
								
								app/dns/dnscommon.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,230 @@ | |||||||
|  | // +build !confonly | ||||||
|  |  | ||||||
|  | package dns | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"encoding/binary" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/xtls/xray-core/v1/common" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/errors" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/net" | ||||||
|  | 	dns_feature "github.com/xtls/xray-core/v1/features/dns" | ||||||
|  | 	"golang.org/x/net/dns/dnsmessage" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Fqdn normalize domain make sure it ends with '.' | ||||||
|  | func Fqdn(domain string) string { | ||||||
|  | 	if len(domain) > 0 && domain[len(domain)-1] == '.' { | ||||||
|  | 		return domain | ||||||
|  | 	} | ||||||
|  | 	return domain + "." | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type record struct { | ||||||
|  | 	A    *IPRecord | ||||||
|  | 	AAAA *IPRecord | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // IPRecord is a cacheable item for a resolved domain | ||||||
|  | type IPRecord struct { | ||||||
|  | 	ReqID  uint16 | ||||||
|  | 	IP     []net.Address | ||||||
|  | 	Expire time.Time | ||||||
|  | 	RCode  dnsmessage.RCode | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r *IPRecord) getIPs() ([]net.Address, error) { | ||||||
|  | 	if r == nil || r.Expire.Before(time.Now()) { | ||||||
|  | 		return nil, errRecordNotFound | ||||||
|  | 	} | ||||||
|  | 	if r.RCode != dnsmessage.RCodeSuccess { | ||||||
|  | 		return nil, dns_feature.RCodeError(r.RCode) | ||||||
|  | 	} | ||||||
|  | 	return r.IP, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func isNewer(baseRec *IPRecord, newRec *IPRecord) bool { | ||||||
|  | 	if newRec == nil { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	if baseRec == nil { | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 	return baseRec.Expire.Before(newRec.Expire) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var ( | ||||||
|  | 	errRecordNotFound = errors.New("record not found") | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type dnsRequest struct { | ||||||
|  | 	reqType dnsmessage.Type | ||||||
|  | 	domain  string | ||||||
|  | 	start   time.Time | ||||||
|  | 	expire  time.Time | ||||||
|  | 	msg     *dnsmessage.Message | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func genEDNS0Options(clientIP net.IP) *dnsmessage.Resource { | ||||||
|  | 	if len(clientIP) == 0 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var netmask int | ||||||
|  | 	var family uint16 | ||||||
|  |  | ||||||
|  | 	if len(clientIP) == 4 { | ||||||
|  | 		family = 1 | ||||||
|  | 		netmask = 24 // 24 for IPV4, 96 for IPv6 | ||||||
|  | 	} else { | ||||||
|  | 		family = 2 | ||||||
|  | 		netmask = 96 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	b := make([]byte, 4) | ||||||
|  | 	binary.BigEndian.PutUint16(b[0:], family) | ||||||
|  | 	b[2] = byte(netmask) | ||||||
|  | 	b[3] = 0 | ||||||
|  | 	switch family { | ||||||
|  | 	case 1: | ||||||
|  | 		ip := clientIP.To4().Mask(net.CIDRMask(netmask, net.IPv4len*8)) | ||||||
|  | 		needLength := (netmask + 8 - 1) / 8 // division rounding up | ||||||
|  | 		b = append(b, ip[:needLength]...) | ||||||
|  | 	case 2: | ||||||
|  | 		ip := clientIP.Mask(net.CIDRMask(netmask, net.IPv6len*8)) | ||||||
|  | 		needLength := (netmask + 8 - 1) / 8 // division rounding up | ||||||
|  | 		b = append(b, ip[:needLength]...) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	const EDNS0SUBNET = 0x08 | ||||||
|  |  | ||||||
|  | 	opt := new(dnsmessage.Resource) | ||||||
|  | 	common.Must(opt.Header.SetEDNS0(1350, 0xfe00, true)) | ||||||
|  |  | ||||||
|  | 	opt.Body = &dnsmessage.OPTResource{ | ||||||
|  | 		Options: []dnsmessage.Option{ | ||||||
|  | 			{ | ||||||
|  | 				Code: EDNS0SUBNET, | ||||||
|  | 				Data: b, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return opt | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func buildReqMsgs(domain string, option IPOption, reqIDGen func() uint16, reqOpts *dnsmessage.Resource) []*dnsRequest { | ||||||
|  | 	qA := dnsmessage.Question{ | ||||||
|  | 		Name:  dnsmessage.MustNewName(domain), | ||||||
|  | 		Type:  dnsmessage.TypeA, | ||||||
|  | 		Class: dnsmessage.ClassINET, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	qAAAA := dnsmessage.Question{ | ||||||
|  | 		Name:  dnsmessage.MustNewName(domain), | ||||||
|  | 		Type:  dnsmessage.TypeAAAA, | ||||||
|  | 		Class: dnsmessage.ClassINET, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var reqs []*dnsRequest | ||||||
|  | 	now := time.Now() | ||||||
|  |  | ||||||
|  | 	if option.IPv4Enable { | ||||||
|  | 		msg := new(dnsmessage.Message) | ||||||
|  | 		msg.Header.ID = reqIDGen() | ||||||
|  | 		msg.Header.RecursionDesired = true | ||||||
|  | 		msg.Questions = []dnsmessage.Question{qA} | ||||||
|  | 		if reqOpts != nil { | ||||||
|  | 			msg.Additionals = append(msg.Additionals, *reqOpts) | ||||||
|  | 		} | ||||||
|  | 		reqs = append(reqs, &dnsRequest{ | ||||||
|  | 			reqType: dnsmessage.TypeA, | ||||||
|  | 			domain:  domain, | ||||||
|  | 			start:   now, | ||||||
|  | 			msg:     msg, | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if option.IPv6Enable { | ||||||
|  | 		msg := new(dnsmessage.Message) | ||||||
|  | 		msg.Header.ID = reqIDGen() | ||||||
|  | 		msg.Header.RecursionDesired = true | ||||||
|  | 		msg.Questions = []dnsmessage.Question{qAAAA} | ||||||
|  | 		if reqOpts != nil { | ||||||
|  | 			msg.Additionals = append(msg.Additionals, *reqOpts) | ||||||
|  | 		} | ||||||
|  | 		reqs = append(reqs, &dnsRequest{ | ||||||
|  | 			reqType: dnsmessage.TypeAAAA, | ||||||
|  | 			domain:  domain, | ||||||
|  | 			start:   now, | ||||||
|  | 			msg:     msg, | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return reqs | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // parseResponse parse DNS answers from the returned payload | ||||||
|  | func parseResponse(payload []byte) (*IPRecord, error) { | ||||||
|  | 	var parser dnsmessage.Parser | ||||||
|  | 	h, err := parser.Start(payload) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, newError("failed to parse DNS response").Base(err).AtWarning() | ||||||
|  | 	} | ||||||
|  | 	if err := parser.SkipAllQuestions(); err != nil { | ||||||
|  | 		return nil, newError("failed to skip questions in DNS response").Base(err).AtWarning() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	now := time.Now() | ||||||
|  | 	ipRecord := &IPRecord{ | ||||||
|  | 		ReqID:  h.ID, | ||||||
|  | 		RCode:  h.RCode, | ||||||
|  | 		Expire: now.Add(time.Second * 600), | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | L: | ||||||
|  | 	for { | ||||||
|  | 		ah, err := parser.AnswerHeader() | ||||||
|  | 		if err != nil { | ||||||
|  | 			if err != dnsmessage.ErrSectionDone { | ||||||
|  | 				newError("failed to parse answer section for domain: ", ah.Name.String()).Base(err).WriteToLog() | ||||||
|  | 			} | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		ttl := ah.TTL | ||||||
|  | 		if ttl == 0 { | ||||||
|  | 			ttl = 600 | ||||||
|  | 		} | ||||||
|  | 		expire := now.Add(time.Duration(ttl) * time.Second) | ||||||
|  | 		if ipRecord.Expire.After(expire) { | ||||||
|  | 			ipRecord.Expire = expire | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		switch ah.Type { | ||||||
|  | 		case dnsmessage.TypeA: | ||||||
|  | 			ans, err := parser.AResource() | ||||||
|  | 			if err != nil { | ||||||
|  | 				newError("failed to parse A record for domain: ", ah.Name).Base(err).WriteToLog() | ||||||
|  | 				break L | ||||||
|  | 			} | ||||||
|  | 			ipRecord.IP = append(ipRecord.IP, net.IPAddress(ans.A[:])) | ||||||
|  | 		case dnsmessage.TypeAAAA: | ||||||
|  | 			ans, err := parser.AAAAResource() | ||||||
|  | 			if err != nil { | ||||||
|  | 				newError("failed to parse A record for domain: ", ah.Name).Base(err).WriteToLog() | ||||||
|  | 				break L | ||||||
|  | 			} | ||||||
|  | 			ipRecord.IP = append(ipRecord.IP, net.IPAddress(ans.AAAA[:])) | ||||||
|  | 		default: | ||||||
|  | 			if err := parser.SkipAnswer(); err != nil { | ||||||
|  | 				newError("failed to skip answer").Base(err).WriteToLog() | ||||||
|  | 				break L | ||||||
|  | 			} | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return ipRecord, nil | ||||||
|  | } | ||||||
							
								
								
									
										160
									
								
								app/dns/dnscommon_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										160
									
								
								app/dns/dnscommon_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,160 @@ | |||||||
|  | // +build !confonly | ||||||
|  |  | ||||||
|  | package dns | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"math/rand" | ||||||
|  | 	"testing" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/google/go-cmp/cmp" | ||||||
|  | 	"github.com/miekg/dns" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/net" | ||||||
|  | 	"golang.org/x/net/dns/dnsmessage" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func Test_parseResponse(t *testing.T) { | ||||||
|  | 	var p [][]byte | ||||||
|  |  | ||||||
|  | 	ans := new(dns.Msg) | ||||||
|  | 	ans.Id = 0 | ||||||
|  | 	p = append(p, common.Must2(ans.Pack()).([]byte)) | ||||||
|  |  | ||||||
|  | 	p = append(p, []byte{}) | ||||||
|  |  | ||||||
|  | 	ans = new(dns.Msg) | ||||||
|  | 	ans.Id = 1 | ||||||
|  | 	ans.Answer = append(ans.Answer, | ||||||
|  | 		common.Must2(dns.NewRR("google.com. IN CNAME m.test.google.com")).(dns.RR), | ||||||
|  | 		common.Must2(dns.NewRR("google.com. IN CNAME fake.google.com")).(dns.RR), | ||||||
|  | 		common.Must2(dns.NewRR("google.com. IN A 8.8.8.8")).(dns.RR), | ||||||
|  | 		common.Must2(dns.NewRR("google.com. IN A 8.8.4.4")).(dns.RR), | ||||||
|  | 	) | ||||||
|  | 	p = append(p, common.Must2(ans.Pack()).([]byte)) | ||||||
|  |  | ||||||
|  | 	ans = new(dns.Msg) | ||||||
|  | 	ans.Id = 2 | ||||||
|  | 	ans.Answer = append(ans.Answer, | ||||||
|  | 		common.Must2(dns.NewRR("google.com. IN CNAME m.test.google.com")).(dns.RR), | ||||||
|  | 		common.Must2(dns.NewRR("google.com. IN CNAME fake.google.com")).(dns.RR), | ||||||
|  | 		common.Must2(dns.NewRR("google.com. IN CNAME m.test.google.com")).(dns.RR), | ||||||
|  | 		common.Must2(dns.NewRR("google.com. IN CNAME test.google.com")).(dns.RR), | ||||||
|  | 		common.Must2(dns.NewRR("google.com. IN AAAA 2001::123:8888")).(dns.RR), | ||||||
|  | 		common.Must2(dns.NewRR("google.com. IN AAAA 2001::123:8844")).(dns.RR), | ||||||
|  | 	) | ||||||
|  | 	p = append(p, common.Must2(ans.Pack()).([]byte)) | ||||||
|  |  | ||||||
|  | 	tests := []struct { | ||||||
|  | 		name    string | ||||||
|  | 		want    *IPRecord | ||||||
|  | 		wantErr bool | ||||||
|  | 	}{ | ||||||
|  | 		{"empty", | ||||||
|  | 			&IPRecord{0, []net.Address(nil), time.Time{}, dnsmessage.RCodeSuccess}, | ||||||
|  | 			false, | ||||||
|  | 		}, | ||||||
|  | 		{"error", | ||||||
|  | 			nil, | ||||||
|  | 			true, | ||||||
|  | 		}, | ||||||
|  | 		{"a record", | ||||||
|  | 			&IPRecord{1, []net.Address{net.ParseAddress("8.8.8.8"), net.ParseAddress("8.8.4.4")}, | ||||||
|  | 				time.Time{}, dnsmessage.RCodeSuccess}, | ||||||
|  | 			false, | ||||||
|  | 		}, | ||||||
|  | 		{"aaaa record", | ||||||
|  | 			&IPRecord{2, []net.Address{net.ParseAddress("2001::123:8888"), net.ParseAddress("2001::123:8844")}, time.Time{}, dnsmessage.RCodeSuccess}, | ||||||
|  | 			false, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 	for i, tt := range tests { | ||||||
|  | 		t.Run(tt.name, func(t *testing.T) { | ||||||
|  | 			got, err := parseResponse(p[i]) | ||||||
|  | 			if (err != nil) != tt.wantErr { | ||||||
|  | 				t.Errorf("handleResponse() error = %v, wantErr %v", err, tt.wantErr) | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if got != nil { | ||||||
|  | 				// reset the time | ||||||
|  | 				got.Expire = time.Time{} | ||||||
|  | 			} | ||||||
|  | 			if cmp.Diff(got, tt.want) != "" { | ||||||
|  | 				t.Errorf(cmp.Diff(got, tt.want)) | ||||||
|  | 				// t.Errorf("handleResponse() = %#v, want %#v", got, tt.want) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func Test_buildReqMsgs(t *testing.T) { | ||||||
|  | 	stubID := func() uint16 { | ||||||
|  | 		return uint16(rand.Uint32()) | ||||||
|  | 	} | ||||||
|  | 	type args struct { | ||||||
|  | 		domain  string | ||||||
|  | 		option  IPOption | ||||||
|  | 		reqOpts *dnsmessage.Resource | ||||||
|  | 	} | ||||||
|  | 	tests := []struct { | ||||||
|  | 		name string | ||||||
|  | 		args args | ||||||
|  | 		want int | ||||||
|  | 	}{ | ||||||
|  | 		{"dual stack", args{"test.com", IPOption{true, true}, nil}, 2}, | ||||||
|  | 		{"ipv4 only", args{"test.com", IPOption{true, false}, nil}, 1}, | ||||||
|  | 		{"ipv6 only", args{"test.com", IPOption{false, true}, nil}, 1}, | ||||||
|  | 		{"none/error", args{"test.com", IPOption{false, false}, nil}, 0}, | ||||||
|  | 	} | ||||||
|  | 	for _, tt := range tests { | ||||||
|  | 		t.Run(tt.name, func(t *testing.T) { | ||||||
|  | 			if got := buildReqMsgs(tt.args.domain, tt.args.option, stubID, tt.args.reqOpts); !(len(got) == tt.want) { | ||||||
|  | 				t.Errorf("buildReqMsgs() = %v, want %v", got, tt.want) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func Test_genEDNS0Options(t *testing.T) { | ||||||
|  | 	type args struct { | ||||||
|  | 		clientIP net.IP | ||||||
|  | 	} | ||||||
|  | 	tests := []struct { | ||||||
|  | 		name string | ||||||
|  | 		args args | ||||||
|  | 		want *dnsmessage.Resource | ||||||
|  | 	}{ | ||||||
|  | 		// TODO: Add test cases. | ||||||
|  | 		{"ipv4", args{net.ParseIP("4.3.2.1")}, nil}, | ||||||
|  | 		{"ipv6", args{net.ParseIP("2001::4321")}, nil}, | ||||||
|  | 	} | ||||||
|  | 	for _, tt := range tests { | ||||||
|  | 		t.Run(tt.name, func(t *testing.T) { | ||||||
|  | 			if got := genEDNS0Options(tt.args.clientIP); got == nil { | ||||||
|  | 				t.Errorf("genEDNS0Options() = %v, want %v", got, tt.want) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestFqdn(t *testing.T) { | ||||||
|  | 	type args struct { | ||||||
|  | 		domain string | ||||||
|  | 	} | ||||||
|  | 	tests := []struct { | ||||||
|  | 		name string | ||||||
|  | 		args args | ||||||
|  | 		want string | ||||||
|  | 	}{ | ||||||
|  | 		{"with fqdn", args{"www.example.com."}, "www.example.com."}, | ||||||
|  | 		{"without fqdn", args{"www.example.com"}, "www.example.com."}, | ||||||
|  | 	} | ||||||
|  | 	for _, tt := range tests { | ||||||
|  | 		t.Run(tt.name, func(t *testing.T) { | ||||||
|  | 			if got := Fqdn(tt.args.domain); got != tt.want { | ||||||
|  | 				t.Errorf("Fqdn() = %v, want %v", got, tt.want) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										383
									
								
								app/dns/dohdns.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										383
									
								
								app/dns/dohdns.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,383 @@ | |||||||
|  | // +build !confonly | ||||||
|  |  | ||||||
|  | package dns | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"context" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"io/ioutil" | ||||||
|  | 	"net/http" | ||||||
|  | 	"net/url" | ||||||
|  | 	"sync" | ||||||
|  | 	"sync/atomic" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/xtls/xray-core/v1/common" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/net" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/protocol/dns" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/session" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/signal/pubsub" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/task" | ||||||
|  | 	dns_feature "github.com/xtls/xray-core/v1/features/dns" | ||||||
|  | 	"github.com/xtls/xray-core/v1/features/routing" | ||||||
|  | 	"github.com/xtls/xray-core/v1/transport/internet" | ||||||
|  | 	"golang.org/x/net/dns/dnsmessage" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // DoHNameServer implemented DNS over HTTPS (RFC8484) Wire Format, | ||||||
|  | // which is compatible with traditional dns over udp(RFC1035), | ||||||
|  | // thus most of the DOH implementation is copied from udpns.go | ||||||
|  | type DoHNameServer struct { | ||||||
|  | 	sync.RWMutex | ||||||
|  | 	ips        map[string]record | ||||||
|  | 	pub        *pubsub.Service | ||||||
|  | 	cleanup    *task.Periodic | ||||||
|  | 	reqID      uint32 | ||||||
|  | 	clientIP   net.IP | ||||||
|  | 	httpClient *http.Client | ||||||
|  | 	dohURL     string | ||||||
|  | 	name       string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // NewDoHNameServer creates DOH client object for remote resolving | ||||||
|  | func NewDoHNameServer(url *url.URL, dispatcher routing.Dispatcher, clientIP net.IP) (*DoHNameServer, error) { | ||||||
|  | 	newError("DNS: created Remote DOH client for ", url.String()).AtInfo().WriteToLog() | ||||||
|  | 	s := baseDOHNameServer(url, "DOH", clientIP) | ||||||
|  |  | ||||||
|  | 	// Dispatched connection will be closed (interrupted) after each request | ||||||
|  | 	// This makes DOH inefficient without a keep-alived connection | ||||||
|  | 	// See: core/app/proxyman/outbound/handler.go:113 | ||||||
|  | 	// Using mux (https request wrapped in a stream layer) improves the situation. | ||||||
|  | 	// Recommend to use NewDoHLocalNameServer (DOHL:) if xray instance is running on | ||||||
|  | 	//  a normal network eg. the server side of xray | ||||||
|  | 	tr := &http.Transport{ | ||||||
|  | 		MaxIdleConns:        30, | ||||||
|  | 		IdleConnTimeout:     90 * time.Second, | ||||||
|  | 		TLSHandshakeTimeout: 30 * time.Second, | ||||||
|  | 		ForceAttemptHTTP2:   true, | ||||||
|  | 		DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { | ||||||
|  | 			dest, err := net.ParseDestination(network + ":" + addr) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, err | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			link, err := dispatcher.Dispatch(ctx, dest) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, err | ||||||
|  | 			} | ||||||
|  | 			return net.NewConnection( | ||||||
|  | 				net.ConnectionInputMulti(link.Writer), | ||||||
|  | 				net.ConnectionOutputMulti(link.Reader), | ||||||
|  | 			), nil | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	dispatchedClient := &http.Client{ | ||||||
|  | 		Transport: tr, | ||||||
|  | 		Timeout:   60 * time.Second, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	s.httpClient = dispatchedClient | ||||||
|  | 	return s, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // NewDoHLocalNameServer creates DOH client object for local resolving | ||||||
|  | func NewDoHLocalNameServer(url *url.URL, clientIP net.IP) *DoHNameServer { | ||||||
|  | 	url.Scheme = "https" | ||||||
|  | 	s := baseDOHNameServer(url, "DOHL", clientIP) | ||||||
|  | 	tr := &http.Transport{ | ||||||
|  | 		IdleConnTimeout:   90 * time.Second, | ||||||
|  | 		ForceAttemptHTTP2: true, | ||||||
|  | 		DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { | ||||||
|  | 			dest, err := net.ParseDestination(network + ":" + addr) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, err | ||||||
|  | 			} | ||||||
|  | 			conn, err := internet.DialSystem(ctx, dest, nil) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, err | ||||||
|  | 			} | ||||||
|  | 			return conn, nil | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 	s.httpClient = &http.Client{ | ||||||
|  | 		Timeout:   time.Second * 180, | ||||||
|  | 		Transport: tr, | ||||||
|  | 	} | ||||||
|  | 	newError("DNS: created Local DOH client for ", url.String()).AtInfo().WriteToLog() | ||||||
|  | 	return s | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func baseDOHNameServer(url *url.URL, prefix string, clientIP net.IP) *DoHNameServer { | ||||||
|  | 	s := &DoHNameServer{ | ||||||
|  | 		ips:      make(map[string]record), | ||||||
|  | 		clientIP: clientIP, | ||||||
|  | 		pub:      pubsub.NewService(), | ||||||
|  | 		name:     prefix + "//" + url.Host, | ||||||
|  | 		dohURL:   url.String(), | ||||||
|  | 	} | ||||||
|  | 	s.cleanup = &task.Periodic{ | ||||||
|  | 		Interval: time.Minute, | ||||||
|  | 		Execute:  s.Cleanup, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return s | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Name returns client name | ||||||
|  | func (s *DoHNameServer) Name() string { | ||||||
|  | 	return s.name | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Cleanup clears expired items from cache | ||||||
|  | func (s *DoHNameServer) Cleanup() error { | ||||||
|  | 	now := time.Now() | ||||||
|  | 	s.Lock() | ||||||
|  | 	defer s.Unlock() | ||||||
|  |  | ||||||
|  | 	if len(s.ips) == 0 { | ||||||
|  | 		return newError("nothing to do. stopping...") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for domain, record := range s.ips { | ||||||
|  | 		if record.A != nil && record.A.Expire.Before(now) { | ||||||
|  | 			record.A = nil | ||||||
|  | 		} | ||||||
|  | 		if record.AAAA != nil && record.AAAA.Expire.Before(now) { | ||||||
|  | 			record.AAAA = nil | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if record.A == nil && record.AAAA == nil { | ||||||
|  | 			newError(s.name, " cleanup ", domain).AtDebug().WriteToLog() | ||||||
|  | 			delete(s.ips, domain) | ||||||
|  | 		} else { | ||||||
|  | 			s.ips[domain] = record | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(s.ips) == 0 { | ||||||
|  | 		s.ips = make(map[string]record) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *DoHNameServer) updateIP(req *dnsRequest, ipRec *IPRecord) { | ||||||
|  | 	elapsed := time.Since(req.start) | ||||||
|  |  | ||||||
|  | 	s.Lock() | ||||||
|  | 	rec := s.ips[req.domain] | ||||||
|  | 	updated := false | ||||||
|  |  | ||||||
|  | 	switch req.reqType { | ||||||
|  | 	case dnsmessage.TypeA: | ||||||
|  | 		if isNewer(rec.A, ipRec) { | ||||||
|  | 			rec.A = ipRec | ||||||
|  | 			updated = true | ||||||
|  | 		} | ||||||
|  | 	case dnsmessage.TypeAAAA: | ||||||
|  | 		addr := make([]net.Address, 0) | ||||||
|  | 		for _, ip := range ipRec.IP { | ||||||
|  | 			if len(ip.IP()) == net.IPv6len { | ||||||
|  | 				addr = append(addr, ip) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		ipRec.IP = addr | ||||||
|  | 		if isNewer(rec.AAAA, ipRec) { | ||||||
|  | 			rec.AAAA = ipRec | ||||||
|  | 			updated = true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	newError(s.name, " got answer: ", req.domain, " ", req.reqType, " -> ", ipRec.IP, " ", elapsed).AtInfo().WriteToLog() | ||||||
|  |  | ||||||
|  | 	if updated { | ||||||
|  | 		s.ips[req.domain] = rec | ||||||
|  | 	} | ||||||
|  | 	switch req.reqType { | ||||||
|  | 	case dnsmessage.TypeA: | ||||||
|  | 		s.pub.Publish(req.domain+"4", nil) | ||||||
|  | 	case dnsmessage.TypeAAAA: | ||||||
|  | 		s.pub.Publish(req.domain+"6", nil) | ||||||
|  | 	} | ||||||
|  | 	s.Unlock() | ||||||
|  | 	common.Must(s.cleanup.Start()) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *DoHNameServer) newReqID() uint16 { | ||||||
|  | 	return uint16(atomic.AddUint32(&s.reqID, 1)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *DoHNameServer) sendQuery(ctx context.Context, domain string, option IPOption) { | ||||||
|  | 	newError(s.name, " querying: ", domain).AtInfo().WriteToLog(session.ExportIDToError(ctx)) | ||||||
|  |  | ||||||
|  | 	reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(s.clientIP)) | ||||||
|  |  | ||||||
|  | 	var deadline time.Time | ||||||
|  | 	if d, ok := ctx.Deadline(); ok { | ||||||
|  | 		deadline = d | ||||||
|  | 	} else { | ||||||
|  | 		deadline = time.Now().Add(time.Second * 5) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, req := range reqs { | ||||||
|  | 		go func(r *dnsRequest) { | ||||||
|  | 			// generate new context for each req, using same context | ||||||
|  | 			// may cause reqs all aborted if any one encounter an error | ||||||
|  | 			dnsCtx := context.Background() | ||||||
|  |  | ||||||
|  | 			// reserve internal dns server requested Inbound | ||||||
|  | 			if inbound := session.InboundFromContext(ctx); inbound != nil { | ||||||
|  | 				dnsCtx = session.ContextWithInbound(dnsCtx, inbound) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			dnsCtx = session.ContextWithContent(dnsCtx, &session.Content{ | ||||||
|  | 				Protocol:      "https", | ||||||
|  | 				SkipRoutePick: true, | ||||||
|  | 			}) | ||||||
|  |  | ||||||
|  | 			// forced to use mux for DOH | ||||||
|  | 			dnsCtx = session.ContextWithMuxPrefered(dnsCtx, true) | ||||||
|  |  | ||||||
|  | 			var cancel context.CancelFunc | ||||||
|  | 			dnsCtx, cancel = context.WithDeadline(dnsCtx, deadline) | ||||||
|  | 			defer cancel() | ||||||
|  |  | ||||||
|  | 			b, err := dns.PackMessage(r.msg) | ||||||
|  | 			if err != nil { | ||||||
|  | 				newError("failed to pack dns query").Base(err).AtError().WriteToLog() | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 			resp, err := s.dohHTTPSContext(dnsCtx, b.Bytes()) | ||||||
|  | 			if err != nil { | ||||||
|  | 				newError("failed to retrieve response").Base(err).AtError().WriteToLog() | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 			rec, err := parseResponse(resp) | ||||||
|  | 			if err != nil { | ||||||
|  | 				newError("failed to handle DOH response").Base(err).AtError().WriteToLog() | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 			s.updateIP(r, rec) | ||||||
|  | 		}(req) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *DoHNameServer) dohHTTPSContext(ctx context.Context, b []byte) ([]byte, error) { | ||||||
|  | 	body := bytes.NewBuffer(b) | ||||||
|  | 	req, err := http.NewRequest("POST", s.dohURL, body) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	req.Header.Add("Accept", "application/dns-message") | ||||||
|  | 	req.Header.Add("Content-Type", "application/dns-message") | ||||||
|  |  | ||||||
|  | 	resp, err := s.httpClient.Do(req.WithContext(ctx)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	defer resp.Body.Close() | ||||||
|  | 	if resp.StatusCode != http.StatusOK { | ||||||
|  | 		io.Copy(ioutil.Discard, resp.Body) // flush resp.Body so that the conn is reusable | ||||||
|  | 		return nil, fmt.Errorf("DOH server returned code %d", resp.StatusCode) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return ioutil.ReadAll(resp.Body) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *DoHNameServer) findIPsForDomain(domain string, option IPOption) ([]net.IP, error) { | ||||||
|  | 	s.RLock() | ||||||
|  | 	record, found := s.ips[domain] | ||||||
|  | 	s.RUnlock() | ||||||
|  |  | ||||||
|  | 	if !found { | ||||||
|  | 		return nil, errRecordNotFound | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var ips []net.Address | ||||||
|  | 	var lastErr error | ||||||
|  | 	if option.IPv6Enable && record.AAAA != nil && record.AAAA.RCode == dnsmessage.RCodeSuccess { | ||||||
|  | 		aaaa, err := record.AAAA.getIPs() | ||||||
|  | 		if err != nil { | ||||||
|  | 			lastErr = err | ||||||
|  | 		} | ||||||
|  | 		ips = append(ips, aaaa...) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if option.IPv4Enable && record.A != nil && record.A.RCode == dnsmessage.RCodeSuccess { | ||||||
|  | 		a, err := record.A.getIPs() | ||||||
|  | 		if err != nil { | ||||||
|  | 			lastErr = err | ||||||
|  | 		} | ||||||
|  | 		ips = append(ips, a...) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(ips) > 0 { | ||||||
|  | 		return toNetIP(ips), nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if lastErr != nil { | ||||||
|  | 		return nil, lastErr | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if (option.IPv4Enable && record.A != nil) || (option.IPv6Enable && record.AAAA != nil) { | ||||||
|  | 		return nil, dns_feature.ErrEmptyResponse | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil, errRecordNotFound | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // QueryIP is called from dns.Server->queryIPTimeout | ||||||
|  | func (s *DoHNameServer) QueryIP(ctx context.Context, domain string, option IPOption) ([]net.IP, error) { | ||||||
|  | 	fqdn := Fqdn(domain) | ||||||
|  |  | ||||||
|  | 	ips, err := s.findIPsForDomain(fqdn, option) | ||||||
|  | 	if err != errRecordNotFound { | ||||||
|  | 		newError(s.name, " cache HIT ", domain, " -> ", ips).Base(err).AtDebug().WriteToLog() | ||||||
|  | 		return ips, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// ipv4 and ipv6 belong to different subscription groups | ||||||
|  | 	var sub4, sub6 *pubsub.Subscriber | ||||||
|  | 	if option.IPv4Enable { | ||||||
|  | 		sub4 = s.pub.Subscribe(fqdn + "4") | ||||||
|  | 		defer sub4.Close() | ||||||
|  | 	} | ||||||
|  | 	if option.IPv6Enable { | ||||||
|  | 		sub6 = s.pub.Subscribe(fqdn + "6") | ||||||
|  | 		defer sub6.Close() | ||||||
|  | 	} | ||||||
|  | 	done := make(chan interface{}) | ||||||
|  | 	go func() { | ||||||
|  | 		if sub4 != nil { | ||||||
|  | 			select { | ||||||
|  | 			case <-sub4.Wait(): | ||||||
|  | 			case <-ctx.Done(): | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		if sub6 != nil { | ||||||
|  | 			select { | ||||||
|  | 			case <-sub6.Wait(): | ||||||
|  | 			case <-ctx.Done(): | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		close(done) | ||||||
|  | 	}() | ||||||
|  | 	s.sendQuery(ctx, fqdn, option) | ||||||
|  |  | ||||||
|  | 	for { | ||||||
|  | 		ips, err := s.findIPsForDomain(fqdn, option) | ||||||
|  | 		if err != errRecordNotFound { | ||||||
|  | 			return ips, err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		select { | ||||||
|  | 		case <-ctx.Done(): | ||||||
|  | 			return nil, ctx.Err() | ||||||
|  | 		case <-done: | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										9
									
								
								app/dns/errors.generated.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								app/dns/errors.generated.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | package dns | ||||||
|  |  | ||||||
|  | import "github.com/xtls/xray-core/v1/common/errors" | ||||||
|  |  | ||||||
|  | type errPathObjHolder struct{} | ||||||
|  |  | ||||||
|  | func newError(values ...interface{}) *errors.Error { | ||||||
|  | 	return errors.New(values...).WithPathObj(errPathObjHolder{}) | ||||||
|  | } | ||||||
							
								
								
									
										124
									
								
								app/dns/hosts.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								app/dns/hosts.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,124 @@ | |||||||
|  | // +build !confonly | ||||||
|  |  | ||||||
|  | package dns | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"github.com/xtls/xray-core/v1/common" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/net" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/strmatcher" | ||||||
|  | 	"github.com/xtls/xray-core/v1/features" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // StaticHosts represents static domain-ip mapping in DNS server. | ||||||
|  | type StaticHosts struct { | ||||||
|  | 	ips      [][]net.Address | ||||||
|  | 	matchers *strmatcher.MatcherGroup | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var typeMap = map[DomainMatchingType]strmatcher.Type{ | ||||||
|  | 	DomainMatchingType_Full:      strmatcher.Full, | ||||||
|  | 	DomainMatchingType_Subdomain: strmatcher.Domain, | ||||||
|  | 	DomainMatchingType_Keyword:   strmatcher.Substr, | ||||||
|  | 	DomainMatchingType_Regex:     strmatcher.Regex, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func toStrMatcher(t DomainMatchingType, domain string) (strmatcher.Matcher, error) { | ||||||
|  | 	strMType, f := typeMap[t] | ||||||
|  | 	if !f { | ||||||
|  | 		return nil, newError("unknown mapping type", t).AtWarning() | ||||||
|  | 	} | ||||||
|  | 	matcher, err := strMType.New(domain) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, newError("failed to create str matcher").Base(err) | ||||||
|  | 	} | ||||||
|  | 	return matcher, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // NewStaticHosts creates a new StaticHosts instance. | ||||||
|  | func NewStaticHosts(hosts []*Config_HostMapping, legacy map[string]*net.IPOrDomain) (*StaticHosts, error) { | ||||||
|  | 	g := new(strmatcher.MatcherGroup) | ||||||
|  | 	sh := &StaticHosts{ | ||||||
|  | 		ips:      make([][]net.Address, len(hosts)+len(legacy)+16), | ||||||
|  | 		matchers: g, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if legacy != nil { | ||||||
|  | 		features.PrintDeprecatedFeatureWarning("simple host mapping") | ||||||
|  |  | ||||||
|  | 		for domain, ip := range legacy { | ||||||
|  | 			matcher, err := strmatcher.Full.New(domain) | ||||||
|  | 			common.Must(err) | ||||||
|  | 			id := g.Add(matcher) | ||||||
|  |  | ||||||
|  | 			address := ip.AsAddress() | ||||||
|  | 			if address.Family().IsDomain() { | ||||||
|  | 				return nil, newError("invalid domain address in static hosts: ", address.Domain()).AtWarning() | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			sh.ips[id] = []net.Address{address} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, mapping := range hosts { | ||||||
|  | 		matcher, err := toStrMatcher(mapping.Type, mapping.Domain) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, newError("failed to create domain matcher").Base(err) | ||||||
|  | 		} | ||||||
|  | 		id := g.Add(matcher) | ||||||
|  | 		ips := make([]net.Address, 0, len(mapping.Ip)+1) | ||||||
|  | 		switch { | ||||||
|  | 		case len(mapping.Ip) > 0: | ||||||
|  | 			for _, ip := range mapping.Ip { | ||||||
|  | 				addr := net.IPAddress(ip) | ||||||
|  | 				if addr == nil { | ||||||
|  | 					return nil, newError("invalid IP address in static hosts: ", ip).AtWarning() | ||||||
|  | 				} | ||||||
|  | 				ips = append(ips, addr) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 		case len(mapping.ProxiedDomain) > 0: | ||||||
|  | 			ips = append(ips, net.DomainAddress(mapping.ProxiedDomain)) | ||||||
|  |  | ||||||
|  | 		default: | ||||||
|  | 			return nil, newError("neither IP address nor proxied domain specified for domain: ", mapping.Domain).AtWarning() | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Special handling for localhost IPv6. This is a dirty workaround as JSON config supports only single IP mapping. | ||||||
|  | 		if len(ips) == 1 && ips[0] == net.LocalHostIP { | ||||||
|  | 			ips = append(ips, net.LocalHostIPv6) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		sh.ips[id] = ips | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return sh, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func filterIP(ips []net.Address, option IPOption) []net.Address { | ||||||
|  | 	filtered := make([]net.Address, 0, len(ips)) | ||||||
|  | 	for _, ip := range ips { | ||||||
|  | 		if (ip.Family().IsIPv4() && option.IPv4Enable) || (ip.Family().IsIPv6() && option.IPv6Enable) { | ||||||
|  | 			filtered = append(filtered, ip) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if len(filtered) == 0 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	return filtered | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // LookupIP returns IP address for the given domain, if exists in this StaticHosts. | ||||||
|  | func (h *StaticHosts) LookupIP(domain string, option IPOption) []net.Address { | ||||||
|  | 	indices := h.matchers.Match(domain) | ||||||
|  | 	if len(indices) == 0 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	ips := []net.Address{} | ||||||
|  | 	for _, id := range indices { | ||||||
|  | 		ips = append(ips, h.ips[id]...) | ||||||
|  | 	} | ||||||
|  | 	if len(ips) == 1 && ips[0].Family().IsDomain() { | ||||||
|  | 		return ips | ||||||
|  | 	} | ||||||
|  | 	return filterIP(ips, option) | ||||||
|  | } | ||||||
							
								
								
									
										79
									
								
								app/dns/hosts_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								app/dns/hosts_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,79 @@ | |||||||
|  | package dns_test | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"github.com/google/go-cmp/cmp" | ||||||
|  |  | ||||||
|  | 	. "github.com/xtls/xray-core/v1/app/dns" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/net" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestStaticHosts(t *testing.T) { | ||||||
|  | 	pb := []*Config_HostMapping{ | ||||||
|  | 		{ | ||||||
|  | 			Type:   DomainMatchingType_Full, | ||||||
|  | 			Domain: "example.com", | ||||||
|  | 			Ip: [][]byte{ | ||||||
|  | 				{1, 1, 1, 1}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Type:   DomainMatchingType_Subdomain, | ||||||
|  | 			Domain: "example.cn", | ||||||
|  | 			Ip: [][]byte{ | ||||||
|  | 				{2, 2, 2, 2}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Type:   DomainMatchingType_Subdomain, | ||||||
|  | 			Domain: "baidu.com", | ||||||
|  | 			Ip: [][]byte{ | ||||||
|  | 				{127, 0, 0, 1}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	hosts, err := NewStaticHosts(pb, nil) | ||||||
|  | 	common.Must(err) | ||||||
|  |  | ||||||
|  | 	{ | ||||||
|  | 		ips := hosts.LookupIP("example.com", IPOption{ | ||||||
|  | 			IPv4Enable: true, | ||||||
|  | 			IPv6Enable: true, | ||||||
|  | 		}) | ||||||
|  | 		if len(ips) != 1 { | ||||||
|  | 			t.Error("expect 1 IP, but got ", len(ips)) | ||||||
|  | 		} | ||||||
|  | 		if diff := cmp.Diff([]byte(ips[0].IP()), []byte{1, 1, 1, 1}); diff != "" { | ||||||
|  | 			t.Error(diff) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	{ | ||||||
|  | 		ips := hosts.LookupIP("www.example.cn", IPOption{ | ||||||
|  | 			IPv4Enable: true, | ||||||
|  | 			IPv6Enable: true, | ||||||
|  | 		}) | ||||||
|  | 		if len(ips) != 1 { | ||||||
|  | 			t.Error("expect 1 IP, but got ", len(ips)) | ||||||
|  | 		} | ||||||
|  | 		if diff := cmp.Diff([]byte(ips[0].IP()), []byte{2, 2, 2, 2}); diff != "" { | ||||||
|  | 			t.Error(diff) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	{ | ||||||
|  | 		ips := hosts.LookupIP("baidu.com", IPOption{ | ||||||
|  | 			IPv4Enable: false, | ||||||
|  | 			IPv6Enable: true, | ||||||
|  | 		}) | ||||||
|  | 		if len(ips) != 1 { | ||||||
|  | 			t.Error("expect 1 IP, but got ", len(ips)) | ||||||
|  | 		} | ||||||
|  | 		if diff := cmp.Diff([]byte(ips[0].IP()), []byte(net.LocalHostIPv6.IP())); diff != "" { | ||||||
|  | 			t.Error(diff) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										56
									
								
								app/dns/nameserver.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								app/dns/nameserver.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | |||||||
|  | // +build !confonly | ||||||
|  |  | ||||||
|  | package dns | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  |  | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/net" | ||||||
|  | 	"github.com/xtls/xray-core/v1/features/dns/localdns" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // IPOption is an object for IP query options. | ||||||
|  | type IPOption struct { | ||||||
|  | 	IPv4Enable bool | ||||||
|  | 	IPv6Enable bool | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Client is the interface for DNS client. | ||||||
|  | type Client interface { | ||||||
|  | 	// Name of the Client. | ||||||
|  | 	Name() string | ||||||
|  |  | ||||||
|  | 	// QueryIP sends IP queries to its configured server. | ||||||
|  | 	QueryIP(ctx context.Context, domain string, option IPOption) ([]net.IP, error) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type LocalNameServer struct { | ||||||
|  | 	client *localdns.Client | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *LocalNameServer) QueryIP(ctx context.Context, domain string, option IPOption) ([]net.IP, error) { | ||||||
|  | 	if option.IPv4Enable && option.IPv6Enable { | ||||||
|  | 		return s.client.LookupIP(domain) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if option.IPv4Enable { | ||||||
|  | 		return s.client.LookupIPv4(domain) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if option.IPv6Enable { | ||||||
|  | 		return s.client.LookupIPv6(domain) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil, newError("neither IPv4 nor IPv6 is enabled") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *LocalNameServer) Name() string { | ||||||
|  | 	return "localhost" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewLocalNameServer() *LocalNameServer { | ||||||
|  | 	newError("DNS: created localhost client").AtInfo().WriteToLog() | ||||||
|  | 	return &LocalNameServer{ | ||||||
|  | 		client: localdns.New(), | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										24
									
								
								app/dns/nameserver_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								app/dns/nameserver_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | |||||||
|  | package dns_test | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"testing" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	. "github.com/xtls/xray-core/v1/app/dns" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestLocalNameServer(t *testing.T) { | ||||||
|  | 	s := NewLocalNameServer() | ||||||
|  | 	ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) | ||||||
|  | 	ips, err := s.QueryIP(ctx, "google.com", IPOption{ | ||||||
|  | 		IPv4Enable: true, | ||||||
|  | 		IPv6Enable: true, | ||||||
|  | 	}) | ||||||
|  | 	cancel() | ||||||
|  | 	common.Must(err) | ||||||
|  | 	if len(ips) == 0 { | ||||||
|  | 		t.Error("expect some ips, but got 0") | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										449
									
								
								app/dns/server.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										449
									
								
								app/dns/server.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,449 @@ | |||||||
|  | // +build !confonly | ||||||
|  |  | ||||||
|  | package dns | ||||||
|  |  | ||||||
|  | //go:generate go run github.com/xtls/xray-core/v1/common/errors/errorgen | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"fmt" | ||||||
|  | 	"log" | ||||||
|  | 	"net/url" | ||||||
|  | 	"strings" | ||||||
|  | 	"sync" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/xtls/xray-core/v1/app/router" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/errors" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/net" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/session" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/strmatcher" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/uuid" | ||||||
|  | 	core "github.com/xtls/xray-core/v1/core" | ||||||
|  | 	"github.com/xtls/xray-core/v1/features" | ||||||
|  | 	"github.com/xtls/xray-core/v1/features/dns" | ||||||
|  | 	"github.com/xtls/xray-core/v1/features/routing" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Server is a DNS rely server. | ||||||
|  | type Server struct { | ||||||
|  | 	sync.Mutex | ||||||
|  | 	hosts         *StaticHosts | ||||||
|  | 	clientIP      net.IP | ||||||
|  | 	clients       []Client             // clientIdx -> Client | ||||||
|  | 	ipIndexMap    []*MultiGeoIPMatcher // clientIdx -> *MultiGeoIPMatcher | ||||||
|  | 	domainRules   [][]string           // clientIdx -> domainRuleIdx -> DomainRule | ||||||
|  | 	domainMatcher strmatcher.IndexMatcher | ||||||
|  | 	matcherInfos  []DomainMatcherInfo // matcherIdx -> DomainMatcherInfo | ||||||
|  | 	tag           string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // DomainMatcherInfo contains information attached to index returned by Server.domainMatcher | ||||||
|  | type DomainMatcherInfo struct { | ||||||
|  | 	clientIdx     uint16 | ||||||
|  | 	domainRuleIdx uint16 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MultiGeoIPMatcher for match | ||||||
|  | type MultiGeoIPMatcher struct { | ||||||
|  | 	matchers []*router.GeoIPMatcher | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var errExpectedIPNonMatch = errors.New("expectIPs not match") | ||||||
|  |  | ||||||
|  | // Match check ip match | ||||||
|  | func (c *MultiGeoIPMatcher) Match(ip net.IP) bool { | ||||||
|  | 	for _, matcher := range c.matchers { | ||||||
|  | 		if matcher.Match(ip) { | ||||||
|  | 			return true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // HasMatcher check has matcher | ||||||
|  | func (c *MultiGeoIPMatcher) HasMatcher() bool { | ||||||
|  | 	return len(c.matchers) > 0 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func generateRandomTag() string { | ||||||
|  | 	id := uuid.New() | ||||||
|  | 	return "xray.system." + id.String() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // New creates a new DNS server with given configuration. | ||||||
|  | func New(ctx context.Context, config *Config) (*Server, error) { | ||||||
|  | 	server := &Server{ | ||||||
|  | 		clients: make([]Client, 0, len(config.NameServers)+len(config.NameServer)), | ||||||
|  | 		tag:     config.Tag, | ||||||
|  | 	} | ||||||
|  | 	if server.tag == "" { | ||||||
|  | 		server.tag = generateRandomTag() | ||||||
|  | 	} | ||||||
|  | 	if len(config.ClientIp) > 0 { | ||||||
|  | 		if len(config.ClientIp) != net.IPv4len && len(config.ClientIp) != net.IPv6len { | ||||||
|  | 			return nil, newError("unexpected IP length", len(config.ClientIp)) | ||||||
|  | 		} | ||||||
|  | 		server.clientIP = net.IP(config.ClientIp) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	hosts, err := NewStaticHosts(config.StaticHosts, config.Hosts) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, newError("failed to create hosts").Base(err) | ||||||
|  | 	} | ||||||
|  | 	server.hosts = hosts | ||||||
|  |  | ||||||
|  | 	addNameServer := func(ns *NameServer) int { | ||||||
|  | 		endpoint := ns.Address | ||||||
|  | 		address := endpoint.Address.AsAddress() | ||||||
|  |  | ||||||
|  | 		switch { | ||||||
|  | 		case address.Family().IsDomain() && address.Domain() == "localhost": | ||||||
|  | 			server.clients = append(server.clients, NewLocalNameServer()) | ||||||
|  | 			// Priotize local domains with specific TLDs or without any dot to local DNS | ||||||
|  | 			// References: | ||||||
|  | 			// https://www.iana.org/assignments/special-use-domain-names/special-use-domain-names.xhtml | ||||||
|  | 			// https://unix.stackexchange.com/questions/92441/whats-the-difference-between-local-home-and-lan | ||||||
|  | 			localTLDsAndDotlessDomains := []*NameServer_PriorityDomain{ | ||||||
|  | 				{Type: DomainMatchingType_Regex, Domain: "^[^.]+$"}, // This will only match domains without any dot | ||||||
|  | 				{Type: DomainMatchingType_Subdomain, Domain: "local"}, | ||||||
|  | 				{Type: DomainMatchingType_Subdomain, Domain: "localdomain"}, | ||||||
|  | 				{Type: DomainMatchingType_Subdomain, Domain: "localhost"}, | ||||||
|  | 				{Type: DomainMatchingType_Subdomain, Domain: "lan"}, | ||||||
|  | 				{Type: DomainMatchingType_Subdomain, Domain: "home.arpa"}, | ||||||
|  | 				{Type: DomainMatchingType_Subdomain, Domain: "example"}, | ||||||
|  | 				{Type: DomainMatchingType_Subdomain, Domain: "invalid"}, | ||||||
|  | 				{Type: DomainMatchingType_Subdomain, Domain: "test"}, | ||||||
|  | 			} | ||||||
|  | 			ns.PrioritizedDomain = append(ns.PrioritizedDomain, localTLDsAndDotlessDomains...) | ||||||
|  |  | ||||||
|  | 		case address.Family().IsDomain() && strings.HasPrefix(address.Domain(), "https+local://"): | ||||||
|  | 			// URI schemed string treated as domain | ||||||
|  | 			// DOH Local mode | ||||||
|  | 			u, err := url.Parse(address.Domain()) | ||||||
|  | 			if err != nil { | ||||||
|  | 				log.Fatalln(newError("DNS config error").Base(err)) | ||||||
|  | 			} | ||||||
|  | 			server.clients = append(server.clients, NewDoHLocalNameServer(u, server.clientIP)) | ||||||
|  |  | ||||||
|  | 		case address.Family().IsDomain() && strings.HasPrefix(address.Domain(), "https://"): | ||||||
|  | 			// DOH Remote mode | ||||||
|  | 			u, err := url.Parse(address.Domain()) | ||||||
|  | 			if err != nil { | ||||||
|  | 				log.Fatalln(newError("DNS config error").Base(err)) | ||||||
|  | 			} | ||||||
|  | 			idx := len(server.clients) | ||||||
|  | 			server.clients = append(server.clients, nil) | ||||||
|  |  | ||||||
|  | 			// need the core dispatcher, register DOHClient at callback | ||||||
|  | 			common.Must(core.RequireFeatures(ctx, func(d routing.Dispatcher) { | ||||||
|  | 				c, err := NewDoHNameServer(u, d, server.clientIP) | ||||||
|  | 				if err != nil { | ||||||
|  | 					log.Fatalln(newError("DNS config error").Base(err)) | ||||||
|  | 				} | ||||||
|  | 				server.clients[idx] = c | ||||||
|  | 			})) | ||||||
|  |  | ||||||
|  | 		default: | ||||||
|  | 			// UDP classic DNS mode | ||||||
|  | 			dest := endpoint.AsDestination() | ||||||
|  | 			if dest.Network == net.Network_Unknown { | ||||||
|  | 				dest.Network = net.Network_UDP | ||||||
|  | 			} | ||||||
|  | 			if dest.Network == net.Network_UDP { | ||||||
|  | 				idx := len(server.clients) | ||||||
|  | 				server.clients = append(server.clients, nil) | ||||||
|  |  | ||||||
|  | 				common.Must(core.RequireFeatures(ctx, func(d routing.Dispatcher) { | ||||||
|  | 					server.clients[idx] = NewClassicNameServer(dest, d, server.clientIP) | ||||||
|  | 				})) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		server.ipIndexMap = append(server.ipIndexMap, nil) | ||||||
|  | 		return len(server.clients) - 1 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(config.NameServers) > 0 { | ||||||
|  | 		features.PrintDeprecatedFeatureWarning("simple DNS server") | ||||||
|  | 		for _, destPB := range config.NameServers { | ||||||
|  | 			addNameServer(&NameServer{Address: destPB}) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(config.NameServer) > 0 { | ||||||
|  | 		clientIndices := []int{} | ||||||
|  | 		domainRuleCount := 0 | ||||||
|  | 		for _, ns := range config.NameServer { | ||||||
|  | 			idx := addNameServer(ns) | ||||||
|  | 			clientIndices = append(clientIndices, idx) | ||||||
|  | 			domainRuleCount += len(ns.PrioritizedDomain) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		domainRules := make([][]string, len(server.clients)) | ||||||
|  | 		domainMatcher := &strmatcher.MatcherGroup{} | ||||||
|  | 		matcherInfos := make([]DomainMatcherInfo, domainRuleCount+1) // matcher index starts from 1 | ||||||
|  | 		var geoIPMatcherContainer router.GeoIPMatcherContainer | ||||||
|  | 		for nidx, ns := range config.NameServer { | ||||||
|  | 			idx := clientIndices[nidx] | ||||||
|  |  | ||||||
|  | 			// Establish domain rule matcher | ||||||
|  | 			rules := []string{} | ||||||
|  | 			ruleCurr := 0 | ||||||
|  | 			ruleIter := 0 | ||||||
|  | 			for _, domain := range ns.PrioritizedDomain { | ||||||
|  | 				matcher, err := toStrMatcher(domain.Type, domain.Domain) | ||||||
|  | 				if err != nil { | ||||||
|  | 					return nil, newError("failed to create prioritized domain").Base(err).AtWarning() | ||||||
|  | 				} | ||||||
|  | 				midx := domainMatcher.Add(matcher) | ||||||
|  | 				if midx >= uint32(len(matcherInfos)) { // This rarely happens according to current matcher's implementation | ||||||
|  | 					newError("expanding domain matcher info array to size ", midx, " when adding ", matcher).AtDebug().WriteToLog() | ||||||
|  | 					matcherInfos = append(matcherInfos, make([]DomainMatcherInfo, midx-uint32(len(matcherInfos))+1)...) | ||||||
|  | 				} | ||||||
|  | 				info := &matcherInfos[midx] | ||||||
|  | 				info.clientIdx = uint16(idx) | ||||||
|  | 				if ruleCurr < len(ns.OriginalRules) { | ||||||
|  | 					info.domainRuleIdx = uint16(ruleCurr) | ||||||
|  | 					rule := ns.OriginalRules[ruleCurr] | ||||||
|  | 					if ruleCurr >= len(rules) { | ||||||
|  | 						rules = append(rules, rule.Rule) | ||||||
|  | 					} | ||||||
|  | 					ruleIter++ | ||||||
|  | 					if ruleIter >= int(rule.Size) { | ||||||
|  | 						ruleIter = 0 | ||||||
|  | 						ruleCurr++ | ||||||
|  | 					} | ||||||
|  | 				} else { // No original rule, generate one according to current domain matcher (majorly for compatibility with tests) | ||||||
|  | 					info.domainRuleIdx = uint16(len(rules)) | ||||||
|  | 					rules = append(rules, matcher.String()) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			domainRules[idx] = rules | ||||||
|  |  | ||||||
|  | 			// only add to ipIndexMap if GeoIP is configured | ||||||
|  | 			if len(ns.Geoip) > 0 { | ||||||
|  | 				var matchers []*router.GeoIPMatcher | ||||||
|  | 				for _, geoip := range ns.Geoip { | ||||||
|  | 					matcher, err := geoIPMatcherContainer.Add(geoip) | ||||||
|  | 					if err != nil { | ||||||
|  | 						return nil, newError("failed to create ip matcher").Base(err).AtWarning() | ||||||
|  | 					} | ||||||
|  | 					matchers = append(matchers, matcher) | ||||||
|  | 				} | ||||||
|  | 				matcher := &MultiGeoIPMatcher{matchers: matchers} | ||||||
|  | 				server.ipIndexMap[idx] = matcher | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		server.domainRules = domainRules | ||||||
|  | 		server.domainMatcher = domainMatcher | ||||||
|  | 		server.matcherInfos = matcherInfos | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(server.clients) == 0 { | ||||||
|  | 		server.clients = append(server.clients, NewLocalNameServer()) | ||||||
|  | 		server.ipIndexMap = append(server.ipIndexMap, nil) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return server, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Type implements common.HasType. | ||||||
|  | func (*Server) Type() interface{} { | ||||||
|  | 	return dns.ClientType() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Start implements common.Runnable. | ||||||
|  | func (s *Server) Start() error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Close implements common.Closable. | ||||||
|  | func (s *Server) Close() error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *Server) IsOwnLink(ctx context.Context) bool { | ||||||
|  | 	inbound := session.InboundFromContext(ctx) | ||||||
|  | 	return inbound != nil && inbound.Tag == s.tag | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Match check dns ip match geoip | ||||||
|  | func (s *Server) Match(idx int, client Client, domain string, ips []net.IP) ([]net.IP, error) { | ||||||
|  | 	var matcher *MultiGeoIPMatcher | ||||||
|  | 	if idx < len(s.ipIndexMap) { | ||||||
|  | 		matcher = s.ipIndexMap[idx] | ||||||
|  | 	} | ||||||
|  | 	if matcher == nil { | ||||||
|  | 		return ips, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if !matcher.HasMatcher() { | ||||||
|  | 		newError("domain ", domain, " server has no valid matcher: ", client.Name(), " idx:", idx).AtDebug().WriteToLog() | ||||||
|  | 		return ips, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	newIps := []net.IP{} | ||||||
|  | 	for _, ip := range ips { | ||||||
|  | 		if matcher.Match(ip) { | ||||||
|  | 			newIps = append(newIps, ip) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if len(newIps) == 0 { | ||||||
|  | 		return nil, errExpectedIPNonMatch | ||||||
|  | 	} | ||||||
|  | 	newError("domain ", domain, " expectIPs ", newIps, " matched at server ", client.Name(), " idx:", idx).AtDebug().WriteToLog() | ||||||
|  | 	return newIps, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *Server) queryIPTimeout(idx int, client Client, domain string, option IPOption) ([]net.IP, error) { | ||||||
|  | 	ctx, cancel := context.WithTimeout(context.Background(), time.Second*4) | ||||||
|  | 	if len(s.tag) > 0 { | ||||||
|  | 		ctx = session.ContextWithInbound(ctx, &session.Inbound{ | ||||||
|  | 			Tag: s.tag, | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | 	ips, err := client.QueryIP(ctx, domain, option) | ||||||
|  | 	cancel() | ||||||
|  |  | ||||||
|  | 	if err != nil { | ||||||
|  | 		return ips, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ips, err = s.Match(idx, client, domain, ips) | ||||||
|  | 	return ips, err | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // LookupIP implements dns.Client. | ||||||
|  | func (s *Server) LookupIP(domain string) ([]net.IP, error) { | ||||||
|  | 	return s.lookupIPInternal(domain, IPOption{ | ||||||
|  | 		IPv4Enable: true, | ||||||
|  | 		IPv6Enable: true, | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // LookupIPv4 implements dns.IPv4Lookup. | ||||||
|  | func (s *Server) LookupIPv4(domain string) ([]net.IP, error) { | ||||||
|  | 	return s.lookupIPInternal(domain, IPOption{ | ||||||
|  | 		IPv4Enable: true, | ||||||
|  | 		IPv6Enable: false, | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // LookupIPv6 implements dns.IPv6Lookup. | ||||||
|  | func (s *Server) LookupIPv6(domain string) ([]net.IP, error) { | ||||||
|  | 	return s.lookupIPInternal(domain, IPOption{ | ||||||
|  | 		IPv4Enable: false, | ||||||
|  | 		IPv6Enable: true, | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *Server) lookupStatic(domain string, option IPOption, depth int32) []net.Address { | ||||||
|  | 	ips := s.hosts.LookupIP(domain, option) | ||||||
|  | 	if ips == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	if ips[0].Family().IsDomain() && depth < 5 { | ||||||
|  | 		if newIPs := s.lookupStatic(ips[0].Domain(), option, depth+1); newIPs != nil { | ||||||
|  | 			return newIPs | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return ips | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func toNetIP(ips []net.Address) []net.IP { | ||||||
|  | 	if len(ips) == 0 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	netips := make([]net.IP, 0, len(ips)) | ||||||
|  | 	for _, ip := range ips { | ||||||
|  | 		netips = append(netips, ip.IP()) | ||||||
|  | 	} | ||||||
|  | 	return netips | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *Server) lookupIPInternal(domain string, option IPOption) ([]net.IP, error) { | ||||||
|  | 	if domain == "" { | ||||||
|  | 		return nil, newError("empty domain name") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// normalize the FQDN form query | ||||||
|  | 	if domain[len(domain)-1] == '.' { | ||||||
|  | 		domain = domain[:len(domain)-1] | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ips := s.lookupStatic(domain, option, 0) | ||||||
|  | 	if ips != nil && ips[0].Family().IsIP() { | ||||||
|  | 		newError("returning ", len(ips), " IPs for domain ", domain).WriteToLog() | ||||||
|  | 		return toNetIP(ips), nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if ips != nil && ips[0].Family().IsDomain() { | ||||||
|  | 		newdomain := ips[0].Domain() | ||||||
|  | 		newError("domain replaced: ", domain, " -> ", newdomain).WriteToLog() | ||||||
|  | 		domain = newdomain | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var lastErr error | ||||||
|  | 	var matchedClient Client | ||||||
|  | 	if s.domainMatcher != nil { | ||||||
|  | 		indices := s.domainMatcher.Match(domain) | ||||||
|  | 		domainRules := []string{} | ||||||
|  | 		matchingDNS := []string{} | ||||||
|  | 		for _, idx := range indices { | ||||||
|  | 			info := s.matcherInfos[idx] | ||||||
|  | 			rule := s.domainRules[info.clientIdx][info.domainRuleIdx] | ||||||
|  | 			domainRules = append(domainRules, fmt.Sprintf("%s(DNS idx:%d)", rule, info.clientIdx)) | ||||||
|  | 			matchingDNS = append(matchingDNS, s.clients[info.clientIdx].Name()) | ||||||
|  | 		} | ||||||
|  | 		if len(domainRules) > 0 { | ||||||
|  | 			newError("domain ", domain, " matches following rules: ", domainRules).AtDebug().WriteToLog() | ||||||
|  | 		} | ||||||
|  | 		if len(matchingDNS) > 0 { | ||||||
|  | 			newError("domain ", domain, " uses following DNS first: ", matchingDNS).AtDebug().WriteToLog() | ||||||
|  | 		} | ||||||
|  | 		for _, idx := range indices { | ||||||
|  | 			clientIdx := int(s.matcherInfos[idx].clientIdx) | ||||||
|  | 			matchedClient = s.clients[clientIdx] | ||||||
|  | 			ips, err := s.queryIPTimeout(clientIdx, matchedClient, domain, option) | ||||||
|  | 			if len(ips) > 0 { | ||||||
|  | 				return ips, nil | ||||||
|  | 			} | ||||||
|  | 			if err == dns.ErrEmptyResponse { | ||||||
|  | 				return nil, err | ||||||
|  | 			} | ||||||
|  | 			if err != nil { | ||||||
|  | 				newError("failed to lookup ip for domain ", domain, " at server ", matchedClient.Name()).Base(err).WriteToLog() | ||||||
|  | 				lastErr = err | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for idx, client := range s.clients { | ||||||
|  | 		if client == matchedClient { | ||||||
|  | 			newError("domain ", domain, " at server ", client.Name(), " idx:", idx, " already lookup failed, just ignore").AtDebug().WriteToLog() | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		ips, err := s.queryIPTimeout(idx, client, domain, option) | ||||||
|  | 		if len(ips) > 0 { | ||||||
|  | 			return ips, nil | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if err != nil { | ||||||
|  | 			newError("failed to lookup ip for domain ", domain, " at server ", client.Name()).Base(err).WriteToLog() | ||||||
|  | 			lastErr = err | ||||||
|  | 		} | ||||||
|  | 		if err != context.Canceled && err != context.DeadlineExceeded && err != errExpectedIPNonMatch { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil, newError("returning nil for domain ", domain).Base(lastErr) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func init() { | ||||||
|  | 	common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { | ||||||
|  | 		return New(ctx, config.(*Config)) | ||||||
|  | 	})) | ||||||
|  | } | ||||||
							
								
								
									
										972
									
								
								app/dns/server_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										972
									
								
								app/dns/server_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,972 @@ | |||||||
|  | package dns_test | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/google/go-cmp/cmp" | ||||||
|  | 	"github.com/miekg/dns" | ||||||
|  |  | ||||||
|  | 	"github.com/xtls/xray-core/v1/app/dispatcher" | ||||||
|  | 	. "github.com/xtls/xray-core/v1/app/dns" | ||||||
|  | 	"github.com/xtls/xray-core/v1/app/policy" | ||||||
|  | 	"github.com/xtls/xray-core/v1/app/proxyman" | ||||||
|  | 	_ "github.com/xtls/xray-core/v1/app/proxyman/outbound" | ||||||
|  | 	"github.com/xtls/xray-core/v1/app/router" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/net" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/serial" | ||||||
|  | 	"github.com/xtls/xray-core/v1/core" | ||||||
|  | 	feature_dns "github.com/xtls/xray-core/v1/features/dns" | ||||||
|  | 	"github.com/xtls/xray-core/v1/proxy/freedom" | ||||||
|  | 	"github.com/xtls/xray-core/v1/testing/servers/udp" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type staticHandler struct { | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (*staticHandler) ServeDNS(w dns.ResponseWriter, r *dns.Msg) { | ||||||
|  | 	ans := new(dns.Msg) | ||||||
|  | 	ans.Id = r.Id | ||||||
|  |  | ||||||
|  | 	var clientIP net.IP | ||||||
|  |  | ||||||
|  | 	opt := r.IsEdns0() | ||||||
|  | 	if opt != nil { | ||||||
|  | 		for _, o := range opt.Option { | ||||||
|  | 			if o.Option() == dns.EDNS0SUBNET { | ||||||
|  | 				subnet := o.(*dns.EDNS0_SUBNET) | ||||||
|  | 				clientIP = subnet.Address | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, q := range r.Question { | ||||||
|  | 		switch { | ||||||
|  | 		case q.Name == "google.com." && q.Qtype == dns.TypeA: | ||||||
|  | 			if clientIP == nil { | ||||||
|  | 				rr, _ := dns.NewRR("google.com. IN A 8.8.8.8") | ||||||
|  | 				ans.Answer = append(ans.Answer, rr) | ||||||
|  | 			} else { | ||||||
|  | 				rr, _ := dns.NewRR("google.com. IN A 8.8.4.4") | ||||||
|  | 				ans.Answer = append(ans.Answer, rr) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 		case q.Name == "api.google.com." && q.Qtype == dns.TypeA: | ||||||
|  | 			rr, _ := dns.NewRR("api.google.com. IN A 8.8.7.7") | ||||||
|  | 			ans.Answer = append(ans.Answer, rr) | ||||||
|  |  | ||||||
|  | 		case q.Name == "v2.api.google.com." && q.Qtype == dns.TypeA: | ||||||
|  | 			rr, _ := dns.NewRR("v2.api.google.com. IN A 8.8.7.8") | ||||||
|  | 			ans.Answer = append(ans.Answer, rr) | ||||||
|  |  | ||||||
|  | 		case q.Name == "facebook.com." && q.Qtype == dns.TypeA: | ||||||
|  | 			rr, _ := dns.NewRR("facebook.com. IN A 9.9.9.9") | ||||||
|  | 			ans.Answer = append(ans.Answer, rr) | ||||||
|  |  | ||||||
|  | 		case q.Name == "ipv6.google.com." && q.Qtype == dns.TypeA: | ||||||
|  | 			rr, err := dns.NewRR("ipv6.google.com. IN A 8.8.8.7") | ||||||
|  | 			common.Must(err) | ||||||
|  | 			ans.Answer = append(ans.Answer, rr) | ||||||
|  |  | ||||||
|  | 		case q.Name == "ipv6.google.com." && q.Qtype == dns.TypeAAAA: | ||||||
|  | 			rr, err := dns.NewRR("ipv6.google.com. IN AAAA 2001:4860:4860::8888") | ||||||
|  | 			common.Must(err) | ||||||
|  | 			ans.Answer = append(ans.Answer, rr) | ||||||
|  |  | ||||||
|  | 		case q.Name == "notexist.google.com." && q.Qtype == dns.TypeAAAA: | ||||||
|  | 			ans.MsgHdr.Rcode = dns.RcodeNameError | ||||||
|  |  | ||||||
|  | 		case q.Name == "hostname." && q.Qtype == dns.TypeA: | ||||||
|  | 			rr, _ := dns.NewRR("hostname. IN A 127.0.0.1") | ||||||
|  | 			ans.Answer = append(ans.Answer, rr) | ||||||
|  |  | ||||||
|  | 		case q.Name == "hostname.local." && q.Qtype == dns.TypeA: | ||||||
|  | 			rr, _ := dns.NewRR("hostname.local. IN A 127.0.0.1") | ||||||
|  | 			ans.Answer = append(ans.Answer, rr) | ||||||
|  |  | ||||||
|  | 		case q.Name == "hostname.localdomain." && q.Qtype == dns.TypeA: | ||||||
|  | 			rr, _ := dns.NewRR("hostname.localdomain. IN A 127.0.0.1") | ||||||
|  | 			ans.Answer = append(ans.Answer, rr) | ||||||
|  |  | ||||||
|  | 		case q.Name == "localhost." && q.Qtype == dns.TypeA: | ||||||
|  | 			rr, _ := dns.NewRR("localhost. IN A 127.0.0.2") | ||||||
|  | 			ans.Answer = append(ans.Answer, rr) | ||||||
|  |  | ||||||
|  | 		case q.Name == "localhost-a." && q.Qtype == dns.TypeA: | ||||||
|  | 			rr, _ := dns.NewRR("localhost-a. IN A 127.0.0.3") | ||||||
|  | 			ans.Answer = append(ans.Answer, rr) | ||||||
|  |  | ||||||
|  | 		case q.Name == "localhost-b." && q.Qtype == dns.TypeA: | ||||||
|  | 			rr, _ := dns.NewRR("localhost-b. IN A 127.0.0.4") | ||||||
|  | 			ans.Answer = append(ans.Answer, rr) | ||||||
|  |  | ||||||
|  | 		case q.Name == "Mijia\\ Cloud." && q.Qtype == dns.TypeA: | ||||||
|  | 			rr, _ := dns.NewRR("Mijia\\ Cloud. IN A 127.0.0.1") | ||||||
|  | 			ans.Answer = append(ans.Answer, rr) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	w.WriteMsg(ans) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestUDPServerSubnet(t *testing.T) { | ||||||
|  | 	port := udp.PickPort() | ||||||
|  |  | ||||||
|  | 	dnsServer := dns.Server{ | ||||||
|  | 		Addr:    "127.0.0.1:" + port.String(), | ||||||
|  | 		Net:     "udp", | ||||||
|  | 		Handler: &staticHandler{}, | ||||||
|  | 		UDPSize: 1200, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	go dnsServer.ListenAndServe() | ||||||
|  | 	time.Sleep(time.Second) | ||||||
|  |  | ||||||
|  | 	config := &core.Config{ | ||||||
|  | 		App: []*serial.TypedMessage{ | ||||||
|  | 			serial.ToTypedMessage(&Config{ | ||||||
|  | 				NameServers: []*net.Endpoint{ | ||||||
|  | 					{ | ||||||
|  | 						Network: net.Network_UDP, | ||||||
|  | 						Address: &net.IPOrDomain{ | ||||||
|  | 							Address: &net.IPOrDomain_Ip{ | ||||||
|  | 								Ip: []byte{127, 0, 0, 1}, | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 						Port: uint32(port), | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 				ClientIp: []byte{7, 8, 9, 10}, | ||||||
|  | 			}), | ||||||
|  | 			serial.ToTypedMessage(&dispatcher.Config{}), | ||||||
|  | 			serial.ToTypedMessage(&proxyman.OutboundConfig{}), | ||||||
|  | 			serial.ToTypedMessage(&policy.Config{}), | ||||||
|  | 		}, | ||||||
|  | 		Outbound: []*core.OutboundHandlerConfig{ | ||||||
|  | 			{ | ||||||
|  | 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}), | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	v, err := core.New(config) | ||||||
|  | 	common.Must(err) | ||||||
|  |  | ||||||
|  | 	client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client) | ||||||
|  |  | ||||||
|  | 	ips, err := client.LookupIP("google.com") | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal("unexpected error: ", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if r := cmp.Diff(ips, []net.IP{{8, 8, 4, 4}}); r != "" { | ||||||
|  | 		t.Fatal(r) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestUDPServer(t *testing.T) { | ||||||
|  | 	port := udp.PickPort() | ||||||
|  |  | ||||||
|  | 	dnsServer := dns.Server{ | ||||||
|  | 		Addr:    "127.0.0.1:" + port.String(), | ||||||
|  | 		Net:     "udp", | ||||||
|  | 		Handler: &staticHandler{}, | ||||||
|  | 		UDPSize: 1200, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	go dnsServer.ListenAndServe() | ||||||
|  | 	time.Sleep(time.Second) | ||||||
|  |  | ||||||
|  | 	config := &core.Config{ | ||||||
|  | 		App: []*serial.TypedMessage{ | ||||||
|  | 			serial.ToTypedMessage(&Config{ | ||||||
|  | 				NameServers: []*net.Endpoint{ | ||||||
|  | 					{ | ||||||
|  | 						Network: net.Network_UDP, | ||||||
|  | 						Address: &net.IPOrDomain{ | ||||||
|  | 							Address: &net.IPOrDomain_Ip{ | ||||||
|  | 								Ip: []byte{127, 0, 0, 1}, | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 						Port: uint32(port), | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}), | ||||||
|  | 			serial.ToTypedMessage(&dispatcher.Config{}), | ||||||
|  | 			serial.ToTypedMessage(&proxyman.OutboundConfig{}), | ||||||
|  | 			serial.ToTypedMessage(&policy.Config{}), | ||||||
|  | 		}, | ||||||
|  | 		Outbound: []*core.OutboundHandlerConfig{ | ||||||
|  | 			{ | ||||||
|  | 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}), | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	v, err := core.New(config) | ||||||
|  | 	common.Must(err) | ||||||
|  |  | ||||||
|  | 	client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client) | ||||||
|  |  | ||||||
|  | 	{ | ||||||
|  | 		ips, err := client.LookupIP("google.com") | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatal("unexpected error: ", err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if r := cmp.Diff(ips, []net.IP{{8, 8, 8, 8}}); r != "" { | ||||||
|  | 			t.Fatal(r) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	{ | ||||||
|  | 		ips, err := client.LookupIP("facebook.com") | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatal("unexpected error: ", err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if r := cmp.Diff(ips, []net.IP{{9, 9, 9, 9}}); r != "" { | ||||||
|  | 			t.Fatal(r) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	{ | ||||||
|  | 		_, err := client.LookupIP("notexist.google.com") | ||||||
|  | 		if err == nil { | ||||||
|  | 			t.Fatal("nil error") | ||||||
|  | 		} | ||||||
|  | 		if r := feature_dns.RCodeFromError(err); r != uint16(dns.RcodeNameError) { | ||||||
|  | 			t.Fatal("expected NameError, but got ", r) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	{ | ||||||
|  | 		clientv6 := client.(feature_dns.IPv6Lookup) | ||||||
|  | 		ips, err := clientv6.LookupIPv6("ipv4only.google.com") | ||||||
|  | 		if err != feature_dns.ErrEmptyResponse { | ||||||
|  | 			t.Fatal("error: ", err) | ||||||
|  | 		} | ||||||
|  | 		if len(ips) != 0 { | ||||||
|  | 			t.Fatal("ips: ", ips) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	dnsServer.Shutdown() | ||||||
|  |  | ||||||
|  | 	{ | ||||||
|  | 		ips, err := client.LookupIP("google.com") | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatal("unexpected error: ", err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if r := cmp.Diff(ips, []net.IP{{8, 8, 8, 8}}); r != "" { | ||||||
|  | 			t.Fatal(r) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestPrioritizedDomain(t *testing.T) { | ||||||
|  | 	port := udp.PickPort() | ||||||
|  |  | ||||||
|  | 	dnsServer := dns.Server{ | ||||||
|  | 		Addr:    "127.0.0.1:" + port.String(), | ||||||
|  | 		Net:     "udp", | ||||||
|  | 		Handler: &staticHandler{}, | ||||||
|  | 		UDPSize: 1200, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	go dnsServer.ListenAndServe() | ||||||
|  | 	time.Sleep(time.Second) | ||||||
|  |  | ||||||
|  | 	config := &core.Config{ | ||||||
|  | 		App: []*serial.TypedMessage{ | ||||||
|  | 			serial.ToTypedMessage(&Config{ | ||||||
|  | 				NameServers: []*net.Endpoint{ | ||||||
|  | 					{ | ||||||
|  | 						Network: net.Network_UDP, | ||||||
|  | 						Address: &net.IPOrDomain{ | ||||||
|  | 							Address: &net.IPOrDomain_Ip{ | ||||||
|  | 								Ip: []byte{127, 0, 0, 1}, | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 						Port: 9999, /* unreachable */ | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 				NameServer: []*NameServer{ | ||||||
|  | 					{ | ||||||
|  | 						Address: &net.Endpoint{ | ||||||
|  | 							Network: net.Network_UDP, | ||||||
|  | 							Address: &net.IPOrDomain{ | ||||||
|  | 								Address: &net.IPOrDomain_Ip{ | ||||||
|  | 									Ip: []byte{127, 0, 0, 1}, | ||||||
|  | 								}, | ||||||
|  | 							}, | ||||||
|  | 							Port: uint32(port), | ||||||
|  | 						}, | ||||||
|  | 						PrioritizedDomain: []*NameServer_PriorityDomain{ | ||||||
|  | 							{ | ||||||
|  | 								Type:   DomainMatchingType_Full, | ||||||
|  | 								Domain: "google.com", | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}), | ||||||
|  | 			serial.ToTypedMessage(&dispatcher.Config{}), | ||||||
|  | 			serial.ToTypedMessage(&proxyman.OutboundConfig{}), | ||||||
|  | 			serial.ToTypedMessage(&policy.Config{}), | ||||||
|  | 		}, | ||||||
|  | 		Outbound: []*core.OutboundHandlerConfig{ | ||||||
|  | 			{ | ||||||
|  | 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}), | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	v, err := core.New(config) | ||||||
|  | 	common.Must(err) | ||||||
|  |  | ||||||
|  | 	client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client) | ||||||
|  |  | ||||||
|  | 	startTime := time.Now() | ||||||
|  |  | ||||||
|  | 	{ | ||||||
|  | 		ips, err := client.LookupIP("google.com") | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatal("unexpected error: ", err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if r := cmp.Diff(ips, []net.IP{{8, 8, 8, 8}}); r != "" { | ||||||
|  | 			t.Fatal(r) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	endTime := time.Now() | ||||||
|  | 	if startTime.After(endTime.Add(time.Second * 2)) { | ||||||
|  | 		t.Error("DNS query doesn't finish in 2 seconds.") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestUDPServerIPv6(t *testing.T) { | ||||||
|  | 	port := udp.PickPort() | ||||||
|  |  | ||||||
|  | 	dnsServer := dns.Server{ | ||||||
|  | 		Addr:    "127.0.0.1:" + port.String(), | ||||||
|  | 		Net:     "udp", | ||||||
|  | 		Handler: &staticHandler{}, | ||||||
|  | 		UDPSize: 1200, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	go dnsServer.ListenAndServe() | ||||||
|  | 	time.Sleep(time.Second) | ||||||
|  |  | ||||||
|  | 	config := &core.Config{ | ||||||
|  | 		App: []*serial.TypedMessage{ | ||||||
|  | 			serial.ToTypedMessage(&Config{ | ||||||
|  | 				NameServers: []*net.Endpoint{ | ||||||
|  | 					{ | ||||||
|  | 						Network: net.Network_UDP, | ||||||
|  | 						Address: &net.IPOrDomain{ | ||||||
|  | 							Address: &net.IPOrDomain_Ip{ | ||||||
|  | 								Ip: []byte{127, 0, 0, 1}, | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 						Port: uint32(port), | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}), | ||||||
|  | 			serial.ToTypedMessage(&dispatcher.Config{}), | ||||||
|  | 			serial.ToTypedMessage(&proxyman.OutboundConfig{}), | ||||||
|  | 			serial.ToTypedMessage(&policy.Config{}), | ||||||
|  | 		}, | ||||||
|  | 		Outbound: []*core.OutboundHandlerConfig{ | ||||||
|  | 			{ | ||||||
|  | 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}), | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	v, err := core.New(config) | ||||||
|  | 	common.Must(err) | ||||||
|  |  | ||||||
|  | 	client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client) | ||||||
|  | 	client6 := client.(feature_dns.IPv6Lookup) | ||||||
|  |  | ||||||
|  | 	{ | ||||||
|  | 		ips, err := client6.LookupIPv6("ipv6.google.com") | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatal("unexpected error: ", err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if r := cmp.Diff(ips, []net.IP{{32, 1, 72, 96, 72, 96, 0, 0, 0, 0, 0, 0, 0, 0, 136, 136}}); r != "" { | ||||||
|  | 			t.Fatal(r) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestStaticHostDomain(t *testing.T) { | ||||||
|  | 	port := udp.PickPort() | ||||||
|  |  | ||||||
|  | 	dnsServer := dns.Server{ | ||||||
|  | 		Addr:    "127.0.0.1:" + port.String(), | ||||||
|  | 		Net:     "udp", | ||||||
|  | 		Handler: &staticHandler{}, | ||||||
|  | 		UDPSize: 1200, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	go dnsServer.ListenAndServe() | ||||||
|  | 	time.Sleep(time.Second) | ||||||
|  |  | ||||||
|  | 	config := &core.Config{ | ||||||
|  | 		App: []*serial.TypedMessage{ | ||||||
|  | 			serial.ToTypedMessage(&Config{ | ||||||
|  | 				NameServers: []*net.Endpoint{ | ||||||
|  | 					{ | ||||||
|  | 						Network: net.Network_UDP, | ||||||
|  | 						Address: &net.IPOrDomain{ | ||||||
|  | 							Address: &net.IPOrDomain_Ip{ | ||||||
|  | 								Ip: []byte{127, 0, 0, 1}, | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 						Port: uint32(port), | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 				StaticHosts: []*Config_HostMapping{ | ||||||
|  | 					{ | ||||||
|  | 						Type:          DomainMatchingType_Full, | ||||||
|  | 						Domain:        "example.com", | ||||||
|  | 						ProxiedDomain: "google.com", | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}), | ||||||
|  | 			serial.ToTypedMessage(&dispatcher.Config{}), | ||||||
|  | 			serial.ToTypedMessage(&proxyman.OutboundConfig{}), | ||||||
|  | 			serial.ToTypedMessage(&policy.Config{}), | ||||||
|  | 		}, | ||||||
|  | 		Outbound: []*core.OutboundHandlerConfig{ | ||||||
|  | 			{ | ||||||
|  | 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}), | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	v, err := core.New(config) | ||||||
|  | 	common.Must(err) | ||||||
|  |  | ||||||
|  | 	client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client) | ||||||
|  |  | ||||||
|  | 	{ | ||||||
|  | 		ips, err := client.LookupIP("example.com") | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatal("unexpected error: ", err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if r := cmp.Diff(ips, []net.IP{{8, 8, 8, 8}}); r != "" { | ||||||
|  | 			t.Fatal(r) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	dnsServer.Shutdown() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestIPMatch(t *testing.T) { | ||||||
|  | 	port := udp.PickPort() | ||||||
|  |  | ||||||
|  | 	dnsServer := dns.Server{ | ||||||
|  | 		Addr:    "127.0.0.1:" + port.String(), | ||||||
|  | 		Net:     "udp", | ||||||
|  | 		Handler: &staticHandler{}, | ||||||
|  | 		UDPSize: 1200, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	go dnsServer.ListenAndServe() | ||||||
|  | 	time.Sleep(time.Second) | ||||||
|  |  | ||||||
|  | 	config := &core.Config{ | ||||||
|  | 		App: []*serial.TypedMessage{ | ||||||
|  | 			serial.ToTypedMessage(&Config{ | ||||||
|  | 				NameServer: []*NameServer{ | ||||||
|  | 					// private dns, not match | ||||||
|  | 					{ | ||||||
|  | 						Address: &net.Endpoint{ | ||||||
|  | 							Network: net.Network_UDP, | ||||||
|  | 							Address: &net.IPOrDomain{ | ||||||
|  | 								Address: &net.IPOrDomain_Ip{ | ||||||
|  | 									Ip: []byte{127, 0, 0, 1}, | ||||||
|  | 								}, | ||||||
|  | 							}, | ||||||
|  | 							Port: uint32(port), | ||||||
|  | 						}, | ||||||
|  | 						Geoip: []*router.GeoIP{ | ||||||
|  | 							{ | ||||||
|  | 								CountryCode: "local", | ||||||
|  | 								Cidr: []*router.CIDR{ | ||||||
|  | 									{ | ||||||
|  | 										// inner ip, will not match | ||||||
|  | 										Ip:     []byte{192, 168, 11, 1}, | ||||||
|  | 										Prefix: 32, | ||||||
|  | 									}, | ||||||
|  | 								}, | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 					// second dns, match ip | ||||||
|  | 					{ | ||||||
|  | 						Address: &net.Endpoint{ | ||||||
|  | 							Network: net.Network_UDP, | ||||||
|  | 							Address: &net.IPOrDomain{ | ||||||
|  | 								Address: &net.IPOrDomain_Ip{ | ||||||
|  | 									Ip: []byte{127, 0, 0, 1}, | ||||||
|  | 								}, | ||||||
|  | 							}, | ||||||
|  | 							Port: uint32(port), | ||||||
|  | 						}, | ||||||
|  | 						Geoip: []*router.GeoIP{ | ||||||
|  | 							{ | ||||||
|  | 								CountryCode: "test", | ||||||
|  | 								Cidr: []*router.CIDR{ | ||||||
|  | 									{ | ||||||
|  | 										Ip:     []byte{8, 8, 8, 8}, | ||||||
|  | 										Prefix: 32, | ||||||
|  | 									}, | ||||||
|  | 								}, | ||||||
|  | 							}, | ||||||
|  | 							{ | ||||||
|  | 								CountryCode: "test", | ||||||
|  | 								Cidr: []*router.CIDR{ | ||||||
|  | 									{ | ||||||
|  | 										Ip:     []byte{8, 8, 8, 4}, | ||||||
|  | 										Prefix: 32, | ||||||
|  | 									}, | ||||||
|  | 								}, | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}), | ||||||
|  | 			serial.ToTypedMessage(&dispatcher.Config{}), | ||||||
|  | 			serial.ToTypedMessage(&proxyman.OutboundConfig{}), | ||||||
|  | 			serial.ToTypedMessage(&policy.Config{}), | ||||||
|  | 		}, | ||||||
|  | 		Outbound: []*core.OutboundHandlerConfig{ | ||||||
|  | 			{ | ||||||
|  | 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}), | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	v, err := core.New(config) | ||||||
|  | 	common.Must(err) | ||||||
|  |  | ||||||
|  | 	client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client) | ||||||
|  |  | ||||||
|  | 	startTime := time.Now() | ||||||
|  |  | ||||||
|  | 	{ | ||||||
|  | 		ips, err := client.LookupIP("google.com") | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatal("unexpected error: ", err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if r := cmp.Diff(ips, []net.IP{{8, 8, 8, 8}}); r != "" { | ||||||
|  | 			t.Fatal(r) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	endTime := time.Now() | ||||||
|  | 	if startTime.After(endTime.Add(time.Second * 2)) { | ||||||
|  | 		t.Error("DNS query doesn't finish in 2 seconds.") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestLocalDomain(t *testing.T) { | ||||||
|  | 	port := udp.PickPort() | ||||||
|  |  | ||||||
|  | 	dnsServer := dns.Server{ | ||||||
|  | 		Addr:    "127.0.0.1:" + port.String(), | ||||||
|  | 		Net:     "udp", | ||||||
|  | 		Handler: &staticHandler{}, | ||||||
|  | 		UDPSize: 1200, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	go dnsServer.ListenAndServe() | ||||||
|  | 	time.Sleep(time.Second) | ||||||
|  |  | ||||||
|  | 	config := &core.Config{ | ||||||
|  | 		App: []*serial.TypedMessage{ | ||||||
|  | 			serial.ToTypedMessage(&Config{ | ||||||
|  | 				NameServers: []*net.Endpoint{ | ||||||
|  | 					{ | ||||||
|  | 						Network: net.Network_UDP, | ||||||
|  | 						Address: &net.IPOrDomain{ | ||||||
|  | 							Address: &net.IPOrDomain_Ip{ | ||||||
|  | 								Ip: []byte{127, 0, 0, 1}, | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 						Port: 9999, /* unreachable */ | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 				NameServer: []*NameServer{ | ||||||
|  | 					{ | ||||||
|  | 						Address: &net.Endpoint{ | ||||||
|  | 							Network: net.Network_UDP, | ||||||
|  | 							Address: &net.IPOrDomain{ | ||||||
|  | 								Address: &net.IPOrDomain_Ip{ | ||||||
|  | 									Ip: []byte{127, 0, 0, 1}, | ||||||
|  | 								}, | ||||||
|  | 							}, | ||||||
|  | 							Port: uint32(port), | ||||||
|  | 						}, | ||||||
|  | 						PrioritizedDomain: []*NameServer_PriorityDomain{ | ||||||
|  | 							// Equivalent of dotless:localhost | ||||||
|  | 							{Type: DomainMatchingType_Regex, Domain: "^[^.]*localhost[^.]*$"}, | ||||||
|  | 						}, | ||||||
|  | 						Geoip: []*router.GeoIP{ | ||||||
|  | 							{ // Will match localhost, localhost-a and localhost-b, | ||||||
|  | 								CountryCode: "local", | ||||||
|  | 								Cidr: []*router.CIDR{ | ||||||
|  | 									{Ip: []byte{127, 0, 0, 2}, Prefix: 32}, | ||||||
|  | 									{Ip: []byte{127, 0, 0, 3}, Prefix: 32}, | ||||||
|  | 									{Ip: []byte{127, 0, 0, 4}, Prefix: 32}, | ||||||
|  | 								}, | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 					{ | ||||||
|  | 						Address: &net.Endpoint{ | ||||||
|  | 							Network: net.Network_UDP, | ||||||
|  | 							Address: &net.IPOrDomain{ | ||||||
|  | 								Address: &net.IPOrDomain_Ip{ | ||||||
|  | 									Ip: []byte{127, 0, 0, 1}, | ||||||
|  | 								}, | ||||||
|  | 							}, | ||||||
|  | 							Port: uint32(port), | ||||||
|  | 						}, | ||||||
|  | 						PrioritizedDomain: []*NameServer_PriorityDomain{ | ||||||
|  | 							// Equivalent of dotless: and domain:local | ||||||
|  | 							{Type: DomainMatchingType_Regex, Domain: "^[^.]*$"}, | ||||||
|  | 							{Type: DomainMatchingType_Subdomain, Domain: "local"}, | ||||||
|  | 							{Type: DomainMatchingType_Subdomain, Domain: "localdomain"}, | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 				StaticHosts: []*Config_HostMapping{ | ||||||
|  | 					{ | ||||||
|  | 						Type:   DomainMatchingType_Full, | ||||||
|  | 						Domain: "hostnamestatic", | ||||||
|  | 						Ip:     [][]byte{{127, 0, 0, 53}}, | ||||||
|  | 					}, | ||||||
|  | 					{ | ||||||
|  | 						Type:          DomainMatchingType_Full, | ||||||
|  | 						Domain:        "hostnamealias", | ||||||
|  | 						ProxiedDomain: "hostname.localdomain", | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}), | ||||||
|  | 			serial.ToTypedMessage(&dispatcher.Config{}), | ||||||
|  | 			serial.ToTypedMessage(&proxyman.OutboundConfig{}), | ||||||
|  | 			serial.ToTypedMessage(&policy.Config{}), | ||||||
|  | 		}, | ||||||
|  | 		Outbound: []*core.OutboundHandlerConfig{ | ||||||
|  | 			{ | ||||||
|  | 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}), | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	v, err := core.New(config) | ||||||
|  | 	common.Must(err) | ||||||
|  |  | ||||||
|  | 	client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client) | ||||||
|  |  | ||||||
|  | 	startTime := time.Now() | ||||||
|  |  | ||||||
|  | 	{ // Will match dotless: | ||||||
|  | 		ips, err := client.LookupIP("hostname") | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatal("unexpected error: ", err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if r := cmp.Diff(ips, []net.IP{{127, 0, 0, 1}}); r != "" { | ||||||
|  | 			t.Fatal(r) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	{ // Will match domain:local | ||||||
|  | 		ips, err := client.LookupIP("hostname.local") | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatal("unexpected error: ", err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if r := cmp.Diff(ips, []net.IP{{127, 0, 0, 1}}); r != "" { | ||||||
|  | 			t.Fatal(r) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	{ // Will match static ip | ||||||
|  | 		ips, err := client.LookupIP("hostnamestatic") | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatal("unexpected error: ", err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if r := cmp.Diff(ips, []net.IP{{127, 0, 0, 53}}); r != "" { | ||||||
|  | 			t.Fatal(r) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	{ // Will match domain replacing | ||||||
|  | 		ips, err := client.LookupIP("hostnamealias") | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatal("unexpected error: ", err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if r := cmp.Diff(ips, []net.IP{{127, 0, 0, 1}}); r != "" { | ||||||
|  | 			t.Fatal(r) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	{ // Will match dotless:localhost, but not expectIPs: 127.0.0.2, 127.0.0.3, then matches at dotless: | ||||||
|  | 		ips, err := client.LookupIP("localhost") | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatal("unexpected error: ", err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if r := cmp.Diff(ips, []net.IP{{127, 0, 0, 2}}); r != "" { | ||||||
|  | 			t.Fatal(r) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	{ // Will match dotless:localhost, and expectIPs: 127.0.0.2, 127.0.0.3 | ||||||
|  | 		ips, err := client.LookupIP("localhost-a") | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatal("unexpected error: ", err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if r := cmp.Diff(ips, []net.IP{{127, 0, 0, 3}}); r != "" { | ||||||
|  | 			t.Fatal(r) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	{ // Will match dotless:localhost, and expectIPs: 127.0.0.2, 127.0.0.3 | ||||||
|  | 		ips, err := client.LookupIP("localhost-b") | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatal("unexpected error: ", err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if r := cmp.Diff(ips, []net.IP{{127, 0, 0, 4}}); r != "" { | ||||||
|  | 			t.Fatal(r) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	{ // Will match dotless: | ||||||
|  | 		ips, err := client.LookupIP("Mijia Cloud") | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatal("unexpected error: ", err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if r := cmp.Diff(ips, []net.IP{{127, 0, 0, 1}}); r != "" { | ||||||
|  | 			t.Fatal(r) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	endTime := time.Now() | ||||||
|  | 	if startTime.After(endTime.Add(time.Second * 2)) { | ||||||
|  | 		t.Error("DNS query doesn't finish in 2 seconds.") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestMultiMatchPrioritizedDomain(t *testing.T) { | ||||||
|  | 	port := udp.PickPort() | ||||||
|  |  | ||||||
|  | 	dnsServer := dns.Server{ | ||||||
|  | 		Addr:    "127.0.0.1:" + port.String(), | ||||||
|  | 		Net:     "udp", | ||||||
|  | 		Handler: &staticHandler{}, | ||||||
|  | 		UDPSize: 1200, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	go dnsServer.ListenAndServe() | ||||||
|  | 	time.Sleep(time.Second) | ||||||
|  |  | ||||||
|  | 	config := &core.Config{ | ||||||
|  | 		App: []*serial.TypedMessage{ | ||||||
|  | 			serial.ToTypedMessage(&Config{ | ||||||
|  | 				NameServers: []*net.Endpoint{ | ||||||
|  | 					{ | ||||||
|  | 						Network: net.Network_UDP, | ||||||
|  | 						Address: &net.IPOrDomain{ | ||||||
|  | 							Address: &net.IPOrDomain_Ip{ | ||||||
|  | 								Ip: []byte{127, 0, 0, 1}, | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 						Port: 9999, /* unreachable */ | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 				NameServer: []*NameServer{ | ||||||
|  | 					{ | ||||||
|  | 						Address: &net.Endpoint{ | ||||||
|  | 							Network: net.Network_UDP, | ||||||
|  | 							Address: &net.IPOrDomain{ | ||||||
|  | 								Address: &net.IPOrDomain_Ip{ | ||||||
|  | 									Ip: []byte{127, 0, 0, 1}, | ||||||
|  | 								}, | ||||||
|  | 							}, | ||||||
|  | 							Port: uint32(port), | ||||||
|  | 						}, | ||||||
|  | 						PrioritizedDomain: []*NameServer_PriorityDomain{ | ||||||
|  | 							{ | ||||||
|  | 								Type:   DomainMatchingType_Subdomain, | ||||||
|  | 								Domain: "google.com", | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 						Geoip: []*router.GeoIP{ | ||||||
|  | 							{ // Will only match 8.8.8.8 and 8.8.4.4 | ||||||
|  | 								Cidr: []*router.CIDR{ | ||||||
|  | 									{Ip: []byte{8, 8, 8, 8}, Prefix: 32}, | ||||||
|  | 									{Ip: []byte{8, 8, 4, 4}, Prefix: 32}, | ||||||
|  | 								}, | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 					{ | ||||||
|  | 						Address: &net.Endpoint{ | ||||||
|  | 							Network: net.Network_UDP, | ||||||
|  | 							Address: &net.IPOrDomain{ | ||||||
|  | 								Address: &net.IPOrDomain_Ip{ | ||||||
|  | 									Ip: []byte{127, 0, 0, 1}, | ||||||
|  | 								}, | ||||||
|  | 							}, | ||||||
|  | 							Port: uint32(port), | ||||||
|  | 						}, | ||||||
|  | 						PrioritizedDomain: []*NameServer_PriorityDomain{ | ||||||
|  | 							{ | ||||||
|  | 								Type:   DomainMatchingType_Subdomain, | ||||||
|  | 								Domain: "google.com", | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 						Geoip: []*router.GeoIP{ | ||||||
|  | 							{ // Will match 8.8.8.8 and 8.8.8.7, etc | ||||||
|  | 								Cidr: []*router.CIDR{ | ||||||
|  | 									{Ip: []byte{8, 8, 8, 7}, Prefix: 24}, | ||||||
|  | 								}, | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 					{ | ||||||
|  | 						Address: &net.Endpoint{ | ||||||
|  | 							Network: net.Network_UDP, | ||||||
|  | 							Address: &net.IPOrDomain{ | ||||||
|  | 								Address: &net.IPOrDomain_Ip{ | ||||||
|  | 									Ip: []byte{127, 0, 0, 1}, | ||||||
|  | 								}, | ||||||
|  | 							}, | ||||||
|  | 							Port: uint32(port), | ||||||
|  | 						}, | ||||||
|  | 						PrioritizedDomain: []*NameServer_PriorityDomain{ | ||||||
|  | 							{ | ||||||
|  | 								Type:   DomainMatchingType_Subdomain, | ||||||
|  | 								Domain: "api.google.com", | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 						Geoip: []*router.GeoIP{ | ||||||
|  | 							{ // Will only match 8.8.7.7 (api.google.com) | ||||||
|  | 								Cidr: []*router.CIDR{ | ||||||
|  | 									{Ip: []byte{8, 8, 7, 7}, Prefix: 32}, | ||||||
|  | 								}, | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 					{ | ||||||
|  | 						Address: &net.Endpoint{ | ||||||
|  | 							Network: net.Network_UDP, | ||||||
|  | 							Address: &net.IPOrDomain{ | ||||||
|  | 								Address: &net.IPOrDomain_Ip{ | ||||||
|  | 									Ip: []byte{127, 0, 0, 1}, | ||||||
|  | 								}, | ||||||
|  | 							}, | ||||||
|  | 							Port: uint32(port), | ||||||
|  | 						}, | ||||||
|  | 						PrioritizedDomain: []*NameServer_PriorityDomain{ | ||||||
|  | 							{ | ||||||
|  | 								Type:   DomainMatchingType_Full, | ||||||
|  | 								Domain: "v2.api.google.com", | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 						Geoip: []*router.GeoIP{ | ||||||
|  | 							{ // Will only match 8.8.7.8 (v2.api.google.com) | ||||||
|  | 								Cidr: []*router.CIDR{ | ||||||
|  | 									{Ip: []byte{8, 8, 7, 8}, Prefix: 32}, | ||||||
|  | 								}, | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}), | ||||||
|  | 			serial.ToTypedMessage(&dispatcher.Config{}), | ||||||
|  | 			serial.ToTypedMessage(&proxyman.OutboundConfig{}), | ||||||
|  | 			serial.ToTypedMessage(&policy.Config{}), | ||||||
|  | 		}, | ||||||
|  | 		Outbound: []*core.OutboundHandlerConfig{ | ||||||
|  | 			{ | ||||||
|  | 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}), | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	v, err := core.New(config) | ||||||
|  | 	common.Must(err) | ||||||
|  |  | ||||||
|  | 	client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client) | ||||||
|  |  | ||||||
|  | 	startTime := time.Now() | ||||||
|  |  | ||||||
|  | 	{ // Will match server 1,2 and server 1 returns expected ip | ||||||
|  | 		ips, err := client.LookupIP("google.com") | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatal("unexpected error: ", err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if r := cmp.Diff(ips, []net.IP{{8, 8, 8, 8}}); r != "" { | ||||||
|  | 			t.Fatal(r) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	{ // Will match server 1,2 and server 1 returns unexpected ip, then server 2 returns expected one | ||||||
|  | 		clientv4 := client.(feature_dns.IPv4Lookup) | ||||||
|  | 		ips, err := clientv4.LookupIPv4("ipv6.google.com") | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatal("unexpected error: ", err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if r := cmp.Diff(ips, []net.IP{{8, 8, 8, 7}}); r != "" { | ||||||
|  | 			t.Fatal(r) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	{ // Will match server 3,1,2 and server 3 returns expected one | ||||||
|  | 		ips, err := client.LookupIP("api.google.com") | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatal("unexpected error: ", err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if r := cmp.Diff(ips, []net.IP{{8, 8, 7, 7}}); r != "" { | ||||||
|  | 			t.Fatal(r) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	{ // Will match server 4,3,1,2 and server 4 returns expected one | ||||||
|  | 		ips, err := client.LookupIP("v2.api.google.com") | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatal("unexpected error: ", err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if r := cmp.Diff(ips, []net.IP{{8, 8, 7, 8}}); r != "" { | ||||||
|  | 			t.Fatal(r) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	endTime := time.Now() | ||||||
|  | 	if startTime.After(endTime.Add(time.Second * 2)) { | ||||||
|  | 		t.Error("DNS query doesn't finish in 2 seconds.") | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										289
									
								
								app/dns/udpns.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										289
									
								
								app/dns/udpns.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,289 @@ | |||||||
|  | // +build !confonly | ||||||
|  |  | ||||||
|  | package dns | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"strings" | ||||||
|  | 	"sync" | ||||||
|  | 	"sync/atomic" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/xtls/xray-core/v1/common" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/net" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/protocol/dns" | ||||||
|  | 	udp_proto "github.com/xtls/xray-core/v1/common/protocol/udp" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/session" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/signal/pubsub" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/task" | ||||||
|  | 	dns_feature "github.com/xtls/xray-core/v1/features/dns" | ||||||
|  | 	"github.com/xtls/xray-core/v1/features/routing" | ||||||
|  | 	"github.com/xtls/xray-core/v1/transport/internet/udp" | ||||||
|  | 	"golang.org/x/net/dns/dnsmessage" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type ClassicNameServer struct { | ||||||
|  | 	sync.RWMutex | ||||||
|  | 	name      string | ||||||
|  | 	address   net.Destination | ||||||
|  | 	ips       map[string]record | ||||||
|  | 	requests  map[uint16]dnsRequest | ||||||
|  | 	pub       *pubsub.Service | ||||||
|  | 	udpServer *udp.Dispatcher | ||||||
|  | 	cleanup   *task.Periodic | ||||||
|  | 	reqID     uint32 | ||||||
|  | 	clientIP  net.IP | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewClassicNameServer(address net.Destination, dispatcher routing.Dispatcher, clientIP net.IP) *ClassicNameServer { | ||||||
|  | 	// default to 53 if unspecific | ||||||
|  | 	if address.Port == 0 { | ||||||
|  | 		address.Port = net.Port(53) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	s := &ClassicNameServer{ | ||||||
|  | 		address:  address, | ||||||
|  | 		ips:      make(map[string]record), | ||||||
|  | 		requests: make(map[uint16]dnsRequest), | ||||||
|  | 		clientIP: clientIP, | ||||||
|  | 		pub:      pubsub.NewService(), | ||||||
|  | 		name:     strings.ToUpper(address.String()), | ||||||
|  | 	} | ||||||
|  | 	s.cleanup = &task.Periodic{ | ||||||
|  | 		Interval: time.Minute, | ||||||
|  | 		Execute:  s.Cleanup, | ||||||
|  | 	} | ||||||
|  | 	s.udpServer = udp.NewDispatcher(dispatcher, s.HandleResponse) | ||||||
|  | 	newError("DNS: created udp client inited for ", address.NetAddr()).AtInfo().WriteToLog() | ||||||
|  | 	return s | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *ClassicNameServer) Name() string { | ||||||
|  | 	return s.name | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *ClassicNameServer) Cleanup() error { | ||||||
|  | 	now := time.Now() | ||||||
|  | 	s.Lock() | ||||||
|  | 	defer s.Unlock() | ||||||
|  |  | ||||||
|  | 	if len(s.ips) == 0 && len(s.requests) == 0 { | ||||||
|  | 		return newError(s.name, " nothing to do. stopping...") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for domain, record := range s.ips { | ||||||
|  | 		if record.A != nil && record.A.Expire.Before(now) { | ||||||
|  | 			record.A = nil | ||||||
|  | 		} | ||||||
|  | 		if record.AAAA != nil && record.AAAA.Expire.Before(now) { | ||||||
|  | 			record.AAAA = nil | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if record.A == nil && record.AAAA == nil { | ||||||
|  | 			delete(s.ips, domain) | ||||||
|  | 		} else { | ||||||
|  | 			s.ips[domain] = record | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(s.ips) == 0 { | ||||||
|  | 		s.ips = make(map[string]record) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for id, req := range s.requests { | ||||||
|  | 		if req.expire.Before(now) { | ||||||
|  | 			delete(s.requests, id) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(s.requests) == 0 { | ||||||
|  | 		s.requests = make(map[uint16]dnsRequest) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *ClassicNameServer) HandleResponse(ctx context.Context, packet *udp_proto.Packet) { | ||||||
|  | 	ipRec, err := parseResponse(packet.Payload.Bytes()) | ||||||
|  | 	if err != nil { | ||||||
|  | 		newError(s.name, " fail to parse responded DNS udp").AtError().WriteToLog() | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	s.Lock() | ||||||
|  | 	id := ipRec.ReqID | ||||||
|  | 	req, ok := s.requests[id] | ||||||
|  | 	if ok { | ||||||
|  | 		// remove the pending request | ||||||
|  | 		delete(s.requests, id) | ||||||
|  | 	} | ||||||
|  | 	s.Unlock() | ||||||
|  | 	if !ok { | ||||||
|  | 		newError(s.name, " cannot find the pending request").AtError().WriteToLog() | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var rec record | ||||||
|  | 	switch req.reqType { | ||||||
|  | 	case dnsmessage.TypeA: | ||||||
|  | 		rec.A = ipRec | ||||||
|  | 	case dnsmessage.TypeAAAA: | ||||||
|  | 		rec.AAAA = ipRec | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	elapsed := time.Since(req.start) | ||||||
|  | 	newError(s.name, " got answer: ", req.domain, " ", req.reqType, " -> ", ipRec.IP, " ", elapsed).AtInfo().WriteToLog() | ||||||
|  | 	if len(req.domain) > 0 && (rec.A != nil || rec.AAAA != nil) { | ||||||
|  | 		s.updateIP(req.domain, rec) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *ClassicNameServer) updateIP(domain string, newRec record) { | ||||||
|  | 	s.Lock() | ||||||
|  |  | ||||||
|  | 	newError(s.name, " updating IP records for domain:", domain).AtDebug().WriteToLog() | ||||||
|  | 	rec := s.ips[domain] | ||||||
|  |  | ||||||
|  | 	updated := false | ||||||
|  | 	if isNewer(rec.A, newRec.A) { | ||||||
|  | 		rec.A = newRec.A | ||||||
|  | 		updated = true | ||||||
|  | 	} | ||||||
|  | 	if isNewer(rec.AAAA, newRec.AAAA) { | ||||||
|  | 		rec.AAAA = newRec.AAAA | ||||||
|  | 		updated = true | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if updated { | ||||||
|  | 		s.ips[domain] = rec | ||||||
|  | 	} | ||||||
|  | 	if newRec.A != nil { | ||||||
|  | 		s.pub.Publish(domain+"4", nil) | ||||||
|  | 	} | ||||||
|  | 	if newRec.AAAA != nil { | ||||||
|  | 		s.pub.Publish(domain+"6", nil) | ||||||
|  | 	} | ||||||
|  | 	s.Unlock() | ||||||
|  | 	common.Must(s.cleanup.Start()) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *ClassicNameServer) newReqID() uint16 { | ||||||
|  | 	return uint16(atomic.AddUint32(&s.reqID, 1)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *ClassicNameServer) addPendingRequest(req *dnsRequest) { | ||||||
|  | 	s.Lock() | ||||||
|  | 	defer s.Unlock() | ||||||
|  |  | ||||||
|  | 	id := req.msg.ID | ||||||
|  | 	req.expire = time.Now().Add(time.Second * 8) | ||||||
|  | 	s.requests[id] = *req | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *ClassicNameServer) sendQuery(ctx context.Context, domain string, option IPOption) { | ||||||
|  | 	newError(s.name, " querying DNS for: ", domain).AtDebug().WriteToLog(session.ExportIDToError(ctx)) | ||||||
|  |  | ||||||
|  | 	reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(s.clientIP)) | ||||||
|  |  | ||||||
|  | 	for _, req := range reqs { | ||||||
|  | 		s.addPendingRequest(req) | ||||||
|  | 		b, _ := dns.PackMessage(req.msg) | ||||||
|  | 		udpCtx := context.Background() | ||||||
|  | 		if inbound := session.InboundFromContext(ctx); inbound != nil { | ||||||
|  | 			udpCtx = session.ContextWithInbound(udpCtx, inbound) | ||||||
|  | 		} | ||||||
|  | 		udpCtx = session.ContextWithContent(udpCtx, &session.Content{ | ||||||
|  | 			Protocol: "dns", | ||||||
|  | 		}) | ||||||
|  | 		s.udpServer.Dispatch(udpCtx, s.address, b) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *ClassicNameServer) findIPsForDomain(domain string, option IPOption) ([]net.IP, error) { | ||||||
|  | 	s.RLock() | ||||||
|  | 	record, found := s.ips[domain] | ||||||
|  | 	s.RUnlock() | ||||||
|  |  | ||||||
|  | 	if !found { | ||||||
|  | 		return nil, errRecordNotFound | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var ips []net.Address | ||||||
|  | 	var lastErr error | ||||||
|  | 	if option.IPv4Enable { | ||||||
|  | 		a, err := record.A.getIPs() | ||||||
|  | 		if err != nil { | ||||||
|  | 			lastErr = err | ||||||
|  | 		} | ||||||
|  | 		ips = append(ips, a...) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if option.IPv6Enable { | ||||||
|  | 		aaaa, err := record.AAAA.getIPs() | ||||||
|  | 		if err != nil { | ||||||
|  | 			lastErr = err | ||||||
|  | 		} | ||||||
|  | 		ips = append(ips, aaaa...) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(ips) > 0 { | ||||||
|  | 		return toNetIP(ips), nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if lastErr != nil { | ||||||
|  | 		return nil, lastErr | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil, dns_feature.ErrEmptyResponse | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *ClassicNameServer) QueryIP(ctx context.Context, domain string, option IPOption) ([]net.IP, error) { | ||||||
|  | 	fqdn := Fqdn(domain) | ||||||
|  |  | ||||||
|  | 	ips, err := s.findIPsForDomain(fqdn, option) | ||||||
|  | 	if err != errRecordNotFound { | ||||||
|  | 		newError(s.name, " cache HIT ", domain, " -> ", ips).Base(err).AtDebug().WriteToLog() | ||||||
|  | 		return ips, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// ipv4 and ipv6 belong to different subscription groups | ||||||
|  | 	var sub4, sub6 *pubsub.Subscriber | ||||||
|  | 	if option.IPv4Enable { | ||||||
|  | 		sub4 = s.pub.Subscribe(fqdn + "4") | ||||||
|  | 		defer sub4.Close() | ||||||
|  | 	} | ||||||
|  | 	if option.IPv6Enable { | ||||||
|  | 		sub6 = s.pub.Subscribe(fqdn + "6") | ||||||
|  | 		defer sub6.Close() | ||||||
|  | 	} | ||||||
|  | 	done := make(chan interface{}) | ||||||
|  | 	go func() { | ||||||
|  | 		if sub4 != nil { | ||||||
|  | 			select { | ||||||
|  | 			case <-sub4.Wait(): | ||||||
|  | 			case <-ctx.Done(): | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		if sub6 != nil { | ||||||
|  | 			select { | ||||||
|  | 			case <-sub6.Wait(): | ||||||
|  | 			case <-ctx.Done(): | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		close(done) | ||||||
|  | 	}() | ||||||
|  | 	s.sendQuery(ctx, fqdn, option) | ||||||
|  |  | ||||||
|  | 	for { | ||||||
|  | 		ips, err := s.findIPsForDomain(fqdn, option) | ||||||
|  | 		if err != errRecordNotFound { | ||||||
|  | 			return ips, err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		select { | ||||||
|  | 		case <-ctx.Done(): | ||||||
|  | 			return nil, ctx.Err() | ||||||
|  | 		case <-done: | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										53
									
								
								app/log/command/command.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								app/log/command/command.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | |||||||
|  | // +build !confonly | ||||||
|  |  | ||||||
|  | package command | ||||||
|  |  | ||||||
|  | //go:generate go run github.com/xtls/xray-core/v1/common/errors/errorgen | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  |  | ||||||
|  | 	grpc "google.golang.org/grpc" | ||||||
|  |  | ||||||
|  | 	"github.com/xtls/xray-core/v1/app/log" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common" | ||||||
|  | 	"github.com/xtls/xray-core/v1/core" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type LoggerServer struct { | ||||||
|  | 	V *core.Instance | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // RestartLogger implements LoggerService. | ||||||
|  | func (s *LoggerServer) RestartLogger(ctx context.Context, request *RestartLoggerRequest) (*RestartLoggerResponse, error) { | ||||||
|  | 	logger := s.V.GetFeature((*log.Instance)(nil)) | ||||||
|  | 	if logger == nil { | ||||||
|  | 		return nil, newError("unable to get logger instance") | ||||||
|  | 	} | ||||||
|  | 	if err := logger.Close(); err != nil { | ||||||
|  | 		return nil, newError("failed to close logger").Base(err) | ||||||
|  | 	} | ||||||
|  | 	if err := logger.Start(); err != nil { | ||||||
|  | 		return nil, newError("failed to start logger").Base(err) | ||||||
|  | 	} | ||||||
|  | 	return &RestartLoggerResponse{}, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *LoggerServer) mustEmbedUnimplementedLoggerServiceServer() {} | ||||||
|  |  | ||||||
|  | type service struct { | ||||||
|  | 	v *core.Instance | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *service) Register(server *grpc.Server) { | ||||||
|  | 	RegisterLoggerServiceServer(server, &LoggerServer{ | ||||||
|  | 		V: s.v, | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func init() { | ||||||
|  | 	common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, cfg interface{}) (interface{}, error) { | ||||||
|  | 		s := core.MustFromContext(ctx) | ||||||
|  | 		return &service{v: s}, nil | ||||||
|  | 	})) | ||||||
|  | } | ||||||
							
								
								
									
										34
									
								
								app/log/command/command_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								app/log/command/command_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  | package command_test | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"github.com/xtls/xray-core/v1/app/dispatcher" | ||||||
|  | 	"github.com/xtls/xray-core/v1/app/log" | ||||||
|  | 	. "github.com/xtls/xray-core/v1/app/log/command" | ||||||
|  | 	"github.com/xtls/xray-core/v1/app/proxyman" | ||||||
|  | 	_ "github.com/xtls/xray-core/v1/app/proxyman/inbound" | ||||||
|  | 	_ "github.com/xtls/xray-core/v1/app/proxyman/outbound" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/serial" | ||||||
|  | 	"github.com/xtls/xray-core/v1/core" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestLoggerRestart(t *testing.T) { | ||||||
|  | 	v, err := core.New(&core.Config{ | ||||||
|  | 		App: []*serial.TypedMessage{ | ||||||
|  | 			serial.ToTypedMessage(&log.Config{}), | ||||||
|  | 			serial.ToTypedMessage(&dispatcher.Config{}), | ||||||
|  | 			serial.ToTypedMessage(&proxyman.InboundConfig{}), | ||||||
|  | 			serial.ToTypedMessage(&proxyman.OutboundConfig{}), | ||||||
|  | 		}, | ||||||
|  | 	}) | ||||||
|  | 	common.Must(err) | ||||||
|  | 	common.Must(v.Start()) | ||||||
|  |  | ||||||
|  | 	server := &LoggerServer{ | ||||||
|  | 		V: v, | ||||||
|  | 	} | ||||||
|  | 	common.Must2(server.RestartLogger(context.Background(), &RestartLoggerRequest{})) | ||||||
|  | } | ||||||
							
								
								
									
										258
									
								
								app/log/command/config.pb.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										258
									
								
								app/log/command/config.pb.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,258 @@ | |||||||
|  | // Code generated by protoc-gen-go. DO NOT EDIT. | ||||||
|  | // versions: | ||||||
|  | // 	protoc-gen-go v1.25.0 | ||||||
|  | // 	protoc        v3.14.0 | ||||||
|  | // source: app/log/command/config.proto | ||||||
|  |  | ||||||
|  | package command | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	proto "github.com/golang/protobuf/proto" | ||||||
|  | 	protoreflect "google.golang.org/protobuf/reflect/protoreflect" | ||||||
|  | 	protoimpl "google.golang.org/protobuf/runtime/protoimpl" | ||||||
|  | 	reflect "reflect" | ||||||
|  | 	sync "sync" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	// Verify that this generated code is sufficiently up-to-date. | ||||||
|  | 	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) | ||||||
|  | 	// Verify that runtime/protoimpl is sufficiently up-to-date. | ||||||
|  | 	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // This is a compile-time assertion that a sufficiently up-to-date version | ||||||
|  | // of the legacy proto package is being used. | ||||||
|  | const _ = proto.ProtoPackageIsVersion4 | ||||||
|  |  | ||||||
|  | type Config struct { | ||||||
|  | 	state         protoimpl.MessageState | ||||||
|  | 	sizeCache     protoimpl.SizeCache | ||||||
|  | 	unknownFields protoimpl.UnknownFields | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Config) Reset() { | ||||||
|  | 	*x = Config{} | ||||||
|  | 	if protoimpl.UnsafeEnabled { | ||||||
|  | 		mi := &file_app_log_command_config_proto_msgTypes[0] | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		ms.StoreMessageInfo(mi) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Config) String() string { | ||||||
|  | 	return protoimpl.X.MessageStringOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (*Config) ProtoMessage() {} | ||||||
|  |  | ||||||
|  | func (x *Config) ProtoReflect() protoreflect.Message { | ||||||
|  | 	mi := &file_app_log_command_config_proto_msgTypes[0] | ||||||
|  | 	if protoimpl.UnsafeEnabled && x != nil { | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		if ms.LoadMessageInfo() == nil { | ||||||
|  | 			ms.StoreMessageInfo(mi) | ||||||
|  | 		} | ||||||
|  | 		return ms | ||||||
|  | 	} | ||||||
|  | 	return mi.MessageOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Deprecated: Use Config.ProtoReflect.Descriptor instead. | ||||||
|  | func (*Config) Descriptor() ([]byte, []int) { | ||||||
|  | 	return file_app_log_command_config_proto_rawDescGZIP(), []int{0} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type RestartLoggerRequest struct { | ||||||
|  | 	state         protoimpl.MessageState | ||||||
|  | 	sizeCache     protoimpl.SizeCache | ||||||
|  | 	unknownFields protoimpl.UnknownFields | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *RestartLoggerRequest) Reset() { | ||||||
|  | 	*x = RestartLoggerRequest{} | ||||||
|  | 	if protoimpl.UnsafeEnabled { | ||||||
|  | 		mi := &file_app_log_command_config_proto_msgTypes[1] | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		ms.StoreMessageInfo(mi) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *RestartLoggerRequest) String() string { | ||||||
|  | 	return protoimpl.X.MessageStringOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (*RestartLoggerRequest) ProtoMessage() {} | ||||||
|  |  | ||||||
|  | func (x *RestartLoggerRequest) ProtoReflect() protoreflect.Message { | ||||||
|  | 	mi := &file_app_log_command_config_proto_msgTypes[1] | ||||||
|  | 	if protoimpl.UnsafeEnabled && x != nil { | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		if ms.LoadMessageInfo() == nil { | ||||||
|  | 			ms.StoreMessageInfo(mi) | ||||||
|  | 		} | ||||||
|  | 		return ms | ||||||
|  | 	} | ||||||
|  | 	return mi.MessageOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Deprecated: Use RestartLoggerRequest.ProtoReflect.Descriptor instead. | ||||||
|  | func (*RestartLoggerRequest) Descriptor() ([]byte, []int) { | ||||||
|  | 	return file_app_log_command_config_proto_rawDescGZIP(), []int{1} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type RestartLoggerResponse struct { | ||||||
|  | 	state         protoimpl.MessageState | ||||||
|  | 	sizeCache     protoimpl.SizeCache | ||||||
|  | 	unknownFields protoimpl.UnknownFields | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *RestartLoggerResponse) Reset() { | ||||||
|  | 	*x = RestartLoggerResponse{} | ||||||
|  | 	if protoimpl.UnsafeEnabled { | ||||||
|  | 		mi := &file_app_log_command_config_proto_msgTypes[2] | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		ms.StoreMessageInfo(mi) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *RestartLoggerResponse) String() string { | ||||||
|  | 	return protoimpl.X.MessageStringOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (*RestartLoggerResponse) ProtoMessage() {} | ||||||
|  |  | ||||||
|  | func (x *RestartLoggerResponse) ProtoReflect() protoreflect.Message { | ||||||
|  | 	mi := &file_app_log_command_config_proto_msgTypes[2] | ||||||
|  | 	if protoimpl.UnsafeEnabled && x != nil { | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		if ms.LoadMessageInfo() == nil { | ||||||
|  | 			ms.StoreMessageInfo(mi) | ||||||
|  | 		} | ||||||
|  | 		return ms | ||||||
|  | 	} | ||||||
|  | 	return mi.MessageOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Deprecated: Use RestartLoggerResponse.ProtoReflect.Descriptor instead. | ||||||
|  | func (*RestartLoggerResponse) Descriptor() ([]byte, []int) { | ||||||
|  | 	return file_app_log_command_config_proto_rawDescGZIP(), []int{2} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var File_app_log_command_config_proto protoreflect.FileDescriptor | ||||||
|  |  | ||||||
|  | var file_app_log_command_config_proto_rawDesc = []byte{ | ||||||
|  | 	0x0a, 0x1c, 0x61, 0x70, 0x70, 0x2f, 0x6c, 0x6f, 0x67, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, | ||||||
|  | 	0x64, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x14, | ||||||
|  | 	0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f, 0x67, 0x2e, 0x63, 0x6f, 0x6d, | ||||||
|  | 	0x6d, 0x61, 0x6e, 0x64, 0x22, 0x08, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x16, | ||||||
|  | 	0x0a, 0x14, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x52, | ||||||
|  | 	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x17, 0x0a, 0x15, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, | ||||||
|  | 	0x74, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, | ||||||
|  | 	0x7b, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, | ||||||
|  | 	0x12, 0x6a, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4c, 0x6f, 0x67, 0x67, 0x65, | ||||||
|  | 	0x72, 0x12, 0x2a, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f, 0x67, | ||||||
|  | 	0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, | ||||||
|  | 	0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, | ||||||
|  | 	0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f, 0x67, 0x2e, 0x63, 0x6f, 0x6d, | ||||||
|  | 	0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4c, 0x6f, 0x67, 0x67, | ||||||
|  | 	0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x61, 0x0a, 0x18, | ||||||
|  | 	0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f, 0x67, | ||||||
|  | 	0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x50, 0x01, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, | ||||||
|  | 	0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, | ||||||
|  | 	0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x6c, 0x6f, 0x67, | ||||||
|  | 	0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0xaa, 0x02, 0x14, 0x58, 0x72, 0x61, 0x79, 0x2e, | ||||||
|  | 	0x41, 0x70, 0x70, 0x2e, 0x4c, 0x6f, 0x67, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x62, | ||||||
|  | 	0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var ( | ||||||
|  | 	file_app_log_command_config_proto_rawDescOnce sync.Once | ||||||
|  | 	file_app_log_command_config_proto_rawDescData = file_app_log_command_config_proto_rawDesc | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func file_app_log_command_config_proto_rawDescGZIP() []byte { | ||||||
|  | 	file_app_log_command_config_proto_rawDescOnce.Do(func() { | ||||||
|  | 		file_app_log_command_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_app_log_command_config_proto_rawDescData) | ||||||
|  | 	}) | ||||||
|  | 	return file_app_log_command_config_proto_rawDescData | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var file_app_log_command_config_proto_msgTypes = make([]protoimpl.MessageInfo, 3) | ||||||
|  | var file_app_log_command_config_proto_goTypes = []interface{}{ | ||||||
|  | 	(*Config)(nil),                // 0: xray.app.log.command.Config | ||||||
|  | 	(*RestartLoggerRequest)(nil),  // 1: xray.app.log.command.RestartLoggerRequest | ||||||
|  | 	(*RestartLoggerResponse)(nil), // 2: xray.app.log.command.RestartLoggerResponse | ||||||
|  | } | ||||||
|  | var file_app_log_command_config_proto_depIdxs = []int32{ | ||||||
|  | 	1, // 0: xray.app.log.command.LoggerService.RestartLogger:input_type -> xray.app.log.command.RestartLoggerRequest | ||||||
|  | 	2, // 1: xray.app.log.command.LoggerService.RestartLogger:output_type -> xray.app.log.command.RestartLoggerResponse | ||||||
|  | 	1, // [1:2] is the sub-list for method output_type | ||||||
|  | 	0, // [0:1] is the sub-list for method input_type | ||||||
|  | 	0, // [0:0] is the sub-list for extension type_name | ||||||
|  | 	0, // [0:0] is the sub-list for extension extendee | ||||||
|  | 	0, // [0:0] is the sub-list for field type_name | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func init() { file_app_log_command_config_proto_init() } | ||||||
|  | func file_app_log_command_config_proto_init() { | ||||||
|  | 	if File_app_log_command_config_proto != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if !protoimpl.UnsafeEnabled { | ||||||
|  | 		file_app_log_command_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { | ||||||
|  | 			switch v := v.(*Config); i { | ||||||
|  | 			case 0: | ||||||
|  | 				return &v.state | ||||||
|  | 			case 1: | ||||||
|  | 				return &v.sizeCache | ||||||
|  | 			case 2: | ||||||
|  | 				return &v.unknownFields | ||||||
|  | 			default: | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		file_app_log_command_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { | ||||||
|  | 			switch v := v.(*RestartLoggerRequest); i { | ||||||
|  | 			case 0: | ||||||
|  | 				return &v.state | ||||||
|  | 			case 1: | ||||||
|  | 				return &v.sizeCache | ||||||
|  | 			case 2: | ||||||
|  | 				return &v.unknownFields | ||||||
|  | 			default: | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		file_app_log_command_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { | ||||||
|  | 			switch v := v.(*RestartLoggerResponse); i { | ||||||
|  | 			case 0: | ||||||
|  | 				return &v.state | ||||||
|  | 			case 1: | ||||||
|  | 				return &v.sizeCache | ||||||
|  | 			case 2: | ||||||
|  | 				return &v.unknownFields | ||||||
|  | 			default: | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	type x struct{} | ||||||
|  | 	out := protoimpl.TypeBuilder{ | ||||||
|  | 		File: protoimpl.DescBuilder{ | ||||||
|  | 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(), | ||||||
|  | 			RawDescriptor: file_app_log_command_config_proto_rawDesc, | ||||||
|  | 			NumEnums:      0, | ||||||
|  | 			NumMessages:   3, | ||||||
|  | 			NumExtensions: 0, | ||||||
|  | 			NumServices:   1, | ||||||
|  | 		}, | ||||||
|  | 		GoTypes:           file_app_log_command_config_proto_goTypes, | ||||||
|  | 		DependencyIndexes: file_app_log_command_config_proto_depIdxs, | ||||||
|  | 		MessageInfos:      file_app_log_command_config_proto_msgTypes, | ||||||
|  | 	}.Build() | ||||||
|  | 	File_app_log_command_config_proto = out.File | ||||||
|  | 	file_app_log_command_config_proto_rawDesc = nil | ||||||
|  | 	file_app_log_command_config_proto_goTypes = nil | ||||||
|  | 	file_app_log_command_config_proto_depIdxs = nil | ||||||
|  | } | ||||||
							
								
								
									
										17
									
								
								app/log/command/config.proto
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								app/log/command/config.proto
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | |||||||
|  | syntax = "proto3"; | ||||||
|  |  | ||||||
|  | package xray.app.log.command; | ||||||
|  | option csharp_namespace = "Xray.App.Log.Command"; | ||||||
|  | option go_package = "github.com/xtls/xray-core/v1/app/log/command"; | ||||||
|  | option java_package = "com.xray.app.log.command"; | ||||||
|  | option java_multiple_files = true; | ||||||
|  |  | ||||||
|  | message Config {} | ||||||
|  |  | ||||||
|  | message RestartLoggerRequest {} | ||||||
|  |  | ||||||
|  | message RestartLoggerResponse {} | ||||||
|  |  | ||||||
|  | service LoggerService { | ||||||
|  |   rpc RestartLogger(RestartLoggerRequest) returns (RestartLoggerResponse) {} | ||||||
|  | } | ||||||
							
								
								
									
										97
									
								
								app/log/command/config_grpc.pb.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								app/log/command/config_grpc.pb.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,97 @@ | |||||||
|  | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. | ||||||
|  |  | ||||||
|  | package command | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	context "context" | ||||||
|  | 	grpc "google.golang.org/grpc" | ||||||
|  | 	codes "google.golang.org/grpc/codes" | ||||||
|  | 	status "google.golang.org/grpc/status" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // This is a compile-time assertion to ensure that this generated file | ||||||
|  | // is compatible with the grpc package it is being compiled against. | ||||||
|  | const _ = grpc.SupportPackageIsVersion7 | ||||||
|  |  | ||||||
|  | // LoggerServiceClient is the client API for LoggerService service. | ||||||
|  | // | ||||||
|  | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. | ||||||
|  | type LoggerServiceClient interface { | ||||||
|  | 	RestartLogger(ctx context.Context, in *RestartLoggerRequest, opts ...grpc.CallOption) (*RestartLoggerResponse, error) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type loggerServiceClient struct { | ||||||
|  | 	cc grpc.ClientConnInterface | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewLoggerServiceClient(cc grpc.ClientConnInterface) LoggerServiceClient { | ||||||
|  | 	return &loggerServiceClient{cc} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *loggerServiceClient) RestartLogger(ctx context.Context, in *RestartLoggerRequest, opts ...grpc.CallOption) (*RestartLoggerResponse, error) { | ||||||
|  | 	out := new(RestartLoggerResponse) | ||||||
|  | 	err := c.cc.Invoke(ctx, "/xray.app.log.command.LoggerService/RestartLogger", in, out, opts...) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return out, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // LoggerServiceServer is the server API for LoggerService service. | ||||||
|  | // All implementations must embed UnimplementedLoggerServiceServer | ||||||
|  | // for forward compatibility | ||||||
|  | type LoggerServiceServer interface { | ||||||
|  | 	RestartLogger(context.Context, *RestartLoggerRequest) (*RestartLoggerResponse, error) | ||||||
|  | 	mustEmbedUnimplementedLoggerServiceServer() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // UnimplementedLoggerServiceServer must be embedded to have forward compatible implementations. | ||||||
|  | type UnimplementedLoggerServiceServer struct { | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (UnimplementedLoggerServiceServer) RestartLogger(context.Context, *RestartLoggerRequest) (*RestartLoggerResponse, error) { | ||||||
|  | 	return nil, status.Errorf(codes.Unimplemented, "method RestartLogger not implemented") | ||||||
|  | } | ||||||
|  | func (UnimplementedLoggerServiceServer) mustEmbedUnimplementedLoggerServiceServer() {} | ||||||
|  |  | ||||||
|  | // UnsafeLoggerServiceServer may be embedded to opt out of forward compatibility for this service. | ||||||
|  | // Use of this interface is not recommended, as added methods to LoggerServiceServer will | ||||||
|  | // result in compilation errors. | ||||||
|  | type UnsafeLoggerServiceServer interface { | ||||||
|  | 	mustEmbedUnimplementedLoggerServiceServer() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func RegisterLoggerServiceServer(s grpc.ServiceRegistrar, srv LoggerServiceServer) { | ||||||
|  | 	s.RegisterService(&_LoggerService_serviceDesc, srv) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func _LoggerService_RestartLogger_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { | ||||||
|  | 	in := new(RestartLoggerRequest) | ||||||
|  | 	if err := dec(in); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	if interceptor == nil { | ||||||
|  | 		return srv.(LoggerServiceServer).RestartLogger(ctx, in) | ||||||
|  | 	} | ||||||
|  | 	info := &grpc.UnaryServerInfo{ | ||||||
|  | 		Server:     srv, | ||||||
|  | 		FullMethod: "/xray.app.log.command.LoggerService/RestartLogger", | ||||||
|  | 	} | ||||||
|  | 	handler := func(ctx context.Context, req interface{}) (interface{}, error) { | ||||||
|  | 		return srv.(LoggerServiceServer).RestartLogger(ctx, req.(*RestartLoggerRequest)) | ||||||
|  | 	} | ||||||
|  | 	return interceptor(ctx, in, info, handler) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var _LoggerService_serviceDesc = grpc.ServiceDesc{ | ||||||
|  | 	ServiceName: "xray.app.log.command.LoggerService", | ||||||
|  | 	HandlerType: (*LoggerServiceServer)(nil), | ||||||
|  | 	Methods: []grpc.MethodDesc{ | ||||||
|  | 		{ | ||||||
|  | 			MethodName: "RestartLogger", | ||||||
|  | 			Handler:    _LoggerService_RestartLogger_Handler, | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | 	Streams:  []grpc.StreamDesc{}, | ||||||
|  | 	Metadata: "app/log/command/config.proto", | ||||||
|  | } | ||||||
							
								
								
									
										9
									
								
								app/log/command/errors.generated.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								app/log/command/errors.generated.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | package command | ||||||
|  |  | ||||||
|  | import "github.com/xtls/xray-core/v1/common/errors" | ||||||
|  |  | ||||||
|  | type errPathObjHolder struct{} | ||||||
|  |  | ||||||
|  | func newError(values ...interface{}) *errors.Error { | ||||||
|  | 	return errors.New(values...).WithPathObj(errPathObjHolder{}) | ||||||
|  | } | ||||||
							
								
								
									
										263
									
								
								app/log/config.pb.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										263
									
								
								app/log/config.pb.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,263 @@ | |||||||
|  | // Code generated by protoc-gen-go. DO NOT EDIT. | ||||||
|  | // versions: | ||||||
|  | // 	protoc-gen-go v1.25.0 | ||||||
|  | // 	protoc        v3.14.0 | ||||||
|  | // source: app/log/config.proto | ||||||
|  |  | ||||||
|  | package log | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	proto "github.com/golang/protobuf/proto" | ||||||
|  | 	log "github.com/xtls/xray-core/v1/common/log" | ||||||
|  | 	protoreflect "google.golang.org/protobuf/reflect/protoreflect" | ||||||
|  | 	protoimpl "google.golang.org/protobuf/runtime/protoimpl" | ||||||
|  | 	reflect "reflect" | ||||||
|  | 	sync "sync" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	// Verify that this generated code is sufficiently up-to-date. | ||||||
|  | 	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) | ||||||
|  | 	// Verify that runtime/protoimpl is sufficiently up-to-date. | ||||||
|  | 	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // This is a compile-time assertion that a sufficiently up-to-date version | ||||||
|  | // of the legacy proto package is being used. | ||||||
|  | const _ = proto.ProtoPackageIsVersion4 | ||||||
|  |  | ||||||
|  | type LogType int32 | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	LogType_None    LogType = 0 | ||||||
|  | 	LogType_Console LogType = 1 | ||||||
|  | 	LogType_File    LogType = 2 | ||||||
|  | 	LogType_Event   LogType = 3 | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Enum value maps for LogType. | ||||||
|  | var ( | ||||||
|  | 	LogType_name = map[int32]string{ | ||||||
|  | 		0: "None", | ||||||
|  | 		1: "Console", | ||||||
|  | 		2: "File", | ||||||
|  | 		3: "Event", | ||||||
|  | 	} | ||||||
|  | 	LogType_value = map[string]int32{ | ||||||
|  | 		"None":    0, | ||||||
|  | 		"Console": 1, | ||||||
|  | 		"File":    2, | ||||||
|  | 		"Event":   3, | ||||||
|  | 	} | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func (x LogType) Enum() *LogType { | ||||||
|  | 	p := new(LogType) | ||||||
|  | 	*p = x | ||||||
|  | 	return p | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x LogType) String() string { | ||||||
|  | 	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (LogType) Descriptor() protoreflect.EnumDescriptor { | ||||||
|  | 	return file_app_log_config_proto_enumTypes[0].Descriptor() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (LogType) Type() protoreflect.EnumType { | ||||||
|  | 	return &file_app_log_config_proto_enumTypes[0] | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x LogType) Number() protoreflect.EnumNumber { | ||||||
|  | 	return protoreflect.EnumNumber(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Deprecated: Use LogType.Descriptor instead. | ||||||
|  | func (LogType) EnumDescriptor() ([]byte, []int) { | ||||||
|  | 	return file_app_log_config_proto_rawDescGZIP(), []int{0} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Config struct { | ||||||
|  | 	state         protoimpl.MessageState | ||||||
|  | 	sizeCache     protoimpl.SizeCache | ||||||
|  | 	unknownFields protoimpl.UnknownFields | ||||||
|  |  | ||||||
|  | 	ErrorLogType  LogType      `protobuf:"varint,1,opt,name=error_log_type,json=errorLogType,proto3,enum=xray.app.log.LogType" json:"error_log_type,omitempty"` | ||||||
|  | 	ErrorLogLevel log.Severity `protobuf:"varint,2,opt,name=error_log_level,json=errorLogLevel,proto3,enum=xray.common.log.Severity" json:"error_log_level,omitempty"` | ||||||
|  | 	ErrorLogPath  string       `protobuf:"bytes,3,opt,name=error_log_path,json=errorLogPath,proto3" json:"error_log_path,omitempty"` | ||||||
|  | 	AccessLogType LogType      `protobuf:"varint,4,opt,name=access_log_type,json=accessLogType,proto3,enum=xray.app.log.LogType" json:"access_log_type,omitempty"` | ||||||
|  | 	AccessLogPath string       `protobuf:"bytes,5,opt,name=access_log_path,json=accessLogPath,proto3" json:"access_log_path,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Config) Reset() { | ||||||
|  | 	*x = Config{} | ||||||
|  | 	if protoimpl.UnsafeEnabled { | ||||||
|  | 		mi := &file_app_log_config_proto_msgTypes[0] | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		ms.StoreMessageInfo(mi) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Config) String() string { | ||||||
|  | 	return protoimpl.X.MessageStringOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (*Config) ProtoMessage() {} | ||||||
|  |  | ||||||
|  | func (x *Config) ProtoReflect() protoreflect.Message { | ||||||
|  | 	mi := &file_app_log_config_proto_msgTypes[0] | ||||||
|  | 	if protoimpl.UnsafeEnabled && x != nil { | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		if ms.LoadMessageInfo() == nil { | ||||||
|  | 			ms.StoreMessageInfo(mi) | ||||||
|  | 		} | ||||||
|  | 		return ms | ||||||
|  | 	} | ||||||
|  | 	return mi.MessageOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Deprecated: Use Config.ProtoReflect.Descriptor instead. | ||||||
|  | func (*Config) Descriptor() ([]byte, []int) { | ||||||
|  | 	return file_app_log_config_proto_rawDescGZIP(), []int{0} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Config) GetErrorLogType() LogType { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.ErrorLogType | ||||||
|  | 	} | ||||||
|  | 	return LogType_None | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Config) GetErrorLogLevel() log.Severity { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.ErrorLogLevel | ||||||
|  | 	} | ||||||
|  | 	return log.Severity_Unknown | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Config) GetErrorLogPath() string { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.ErrorLogPath | ||||||
|  | 	} | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Config) GetAccessLogType() LogType { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.AccessLogType | ||||||
|  | 	} | ||||||
|  | 	return LogType_None | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Config) GetAccessLogPath() string { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.AccessLogPath | ||||||
|  | 	} | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var File_app_log_config_proto protoreflect.FileDescriptor | ||||||
|  |  | ||||||
|  | var file_app_log_config_proto_rawDesc = []byte{ | ||||||
|  | 	0x0a, 0x14, 0x61, 0x70, 0x70, 0x2f, 0x6c, 0x6f, 0x67, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, | ||||||
|  | 	0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0c, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, | ||||||
|  | 	0x2e, 0x6c, 0x6f, 0x67, 0x1a, 0x14, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6c, 0x6f, 0x67, | ||||||
|  | 	0x2f, 0x6c, 0x6f, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x95, 0x02, 0x0a, 0x06, 0x43, | ||||||
|  | 	0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3b, 0x0a, 0x0e, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6c, | ||||||
|  | 	0x6f, 0x67, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, | ||||||
|  | 	0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f, 0x67, 0x2e, 0x4c, 0x6f, 0x67, | ||||||
|  | 	0x54, 0x79, 0x70, 0x65, 0x52, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4c, 0x6f, 0x67, 0x54, 0x79, | ||||||
|  | 	0x70, 0x65, 0x12, 0x41, 0x0a, 0x0f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6c, 0x6f, 0x67, 0x5f, | ||||||
|  | 	0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x78, 0x72, | ||||||
|  | 	0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6c, 0x6f, 0x67, 0x2e, 0x53, 0x65, | ||||||
|  | 	0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x52, 0x0d, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4c, 0x6f, 0x67, | ||||||
|  | 	0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x24, 0x0a, 0x0e, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6c, | ||||||
|  | 	0x6f, 0x67, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, | ||||||
|  | 	0x72, 0x72, 0x6f, 0x72, 0x4c, 0x6f, 0x67, 0x50, 0x61, 0x74, 0x68, 0x12, 0x3d, 0x0a, 0x0f, 0x61, | ||||||
|  | 	0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x6c, 0x6f, 0x67, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, | ||||||
|  | 	0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, | ||||||
|  | 	0x6c, 0x6f, 0x67, 0x2e, 0x4c, 0x6f, 0x67, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0d, 0x61, 0x63, 0x63, | ||||||
|  | 	0x65, 0x73, 0x73, 0x4c, 0x6f, 0x67, 0x54, 0x79, 0x70, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x61, 0x63, | ||||||
|  | 	0x63, 0x65, 0x73, 0x73, 0x5f, 0x6c, 0x6f, 0x67, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x05, 0x20, | ||||||
|  | 	0x01, 0x28, 0x09, 0x52, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x6f, 0x67, 0x50, 0x61, | ||||||
|  | 	0x74, 0x68, 0x2a, 0x35, 0x0a, 0x07, 0x4c, 0x6f, 0x67, 0x54, 0x79, 0x70, 0x65, 0x12, 0x08, 0x0a, | ||||||
|  | 	0x04, 0x4e, 0x6f, 0x6e, 0x65, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x73, 0x6f, | ||||||
|  | 	0x6c, 0x65, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x46, 0x69, 0x6c, 0x65, 0x10, 0x02, 0x12, 0x09, | ||||||
|  | 	0x0a, 0x05, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x10, 0x03, 0x42, 0x49, 0x0a, 0x10, 0x63, 0x6f, 0x6d, | ||||||
|  | 	0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f, 0x67, 0x50, 0x01, 0x5a, | ||||||
|  | 	0x24, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, | ||||||
|  | 	0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x70, | ||||||
|  | 	0x70, 0x2f, 0x6c, 0x6f, 0x67, 0xaa, 0x02, 0x0c, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, | ||||||
|  | 	0x2e, 0x4c, 0x6f, 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var ( | ||||||
|  | 	file_app_log_config_proto_rawDescOnce sync.Once | ||||||
|  | 	file_app_log_config_proto_rawDescData = file_app_log_config_proto_rawDesc | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func file_app_log_config_proto_rawDescGZIP() []byte { | ||||||
|  | 	file_app_log_config_proto_rawDescOnce.Do(func() { | ||||||
|  | 		file_app_log_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_app_log_config_proto_rawDescData) | ||||||
|  | 	}) | ||||||
|  | 	return file_app_log_config_proto_rawDescData | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var file_app_log_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1) | ||||||
|  | var file_app_log_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1) | ||||||
|  | var file_app_log_config_proto_goTypes = []interface{}{ | ||||||
|  | 	(LogType)(0),      // 0: xray.app.log.LogType | ||||||
|  | 	(*Config)(nil),    // 1: xray.app.log.Config | ||||||
|  | 	(log.Severity)(0), // 2: xray.common.log.Severity | ||||||
|  | } | ||||||
|  | var file_app_log_config_proto_depIdxs = []int32{ | ||||||
|  | 	0, // 0: xray.app.log.Config.error_log_type:type_name -> xray.app.log.LogType | ||||||
|  | 	2, // 1: xray.app.log.Config.error_log_level:type_name -> xray.common.log.Severity | ||||||
|  | 	0, // 2: xray.app.log.Config.access_log_type:type_name -> xray.app.log.LogType | ||||||
|  | 	3, // [3:3] is the sub-list for method output_type | ||||||
|  | 	3, // [3:3] is the sub-list for method input_type | ||||||
|  | 	3, // [3:3] is the sub-list for extension type_name | ||||||
|  | 	3, // [3:3] is the sub-list for extension extendee | ||||||
|  | 	0, // [0:3] is the sub-list for field type_name | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func init() { file_app_log_config_proto_init() } | ||||||
|  | func file_app_log_config_proto_init() { | ||||||
|  | 	if File_app_log_config_proto != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if !protoimpl.UnsafeEnabled { | ||||||
|  | 		file_app_log_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { | ||||||
|  | 			switch v := v.(*Config); i { | ||||||
|  | 			case 0: | ||||||
|  | 				return &v.state | ||||||
|  | 			case 1: | ||||||
|  | 				return &v.sizeCache | ||||||
|  | 			case 2: | ||||||
|  | 				return &v.unknownFields | ||||||
|  | 			default: | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	type x struct{} | ||||||
|  | 	out := protoimpl.TypeBuilder{ | ||||||
|  | 		File: protoimpl.DescBuilder{ | ||||||
|  | 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(), | ||||||
|  | 			RawDescriptor: file_app_log_config_proto_rawDesc, | ||||||
|  | 			NumEnums:      1, | ||||||
|  | 			NumMessages:   1, | ||||||
|  | 			NumExtensions: 0, | ||||||
|  | 			NumServices:   0, | ||||||
|  | 		}, | ||||||
|  | 		GoTypes:           file_app_log_config_proto_goTypes, | ||||||
|  | 		DependencyIndexes: file_app_log_config_proto_depIdxs, | ||||||
|  | 		EnumInfos:         file_app_log_config_proto_enumTypes, | ||||||
|  | 		MessageInfos:      file_app_log_config_proto_msgTypes, | ||||||
|  | 	}.Build() | ||||||
|  | 	File_app_log_config_proto = out.File | ||||||
|  | 	file_app_log_config_proto_rawDesc = nil | ||||||
|  | 	file_app_log_config_proto_goTypes = nil | ||||||
|  | 	file_app_log_config_proto_depIdxs = nil | ||||||
|  | } | ||||||
							
								
								
									
										25
									
								
								app/log/config.proto
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								app/log/config.proto
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | syntax = "proto3"; | ||||||
|  |  | ||||||
|  | package xray.app.log; | ||||||
|  | option csharp_namespace = "Xray.App.Log"; | ||||||
|  | option go_package = "github.com/xtls/xray-core/v1/app/log"; | ||||||
|  | option java_package = "com.xray.app.log"; | ||||||
|  | option java_multiple_files = true; | ||||||
|  |  | ||||||
|  | import "common/log/log.proto"; | ||||||
|  |  | ||||||
|  | enum LogType { | ||||||
|  |   None = 0; | ||||||
|  |   Console = 1; | ||||||
|  |   File = 2; | ||||||
|  |   Event = 3; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message Config { | ||||||
|  |   LogType error_log_type = 1; | ||||||
|  |   xray.common.log.Severity error_log_level = 2; | ||||||
|  |   string error_log_path = 3; | ||||||
|  |  | ||||||
|  |   LogType access_log_type = 4; | ||||||
|  |   string access_log_path = 5; | ||||||
|  | } | ||||||
							
								
								
									
										9
									
								
								app/log/errors.generated.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								app/log/errors.generated.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | package log | ||||||
|  |  | ||||||
|  | import "github.com/xtls/xray-core/v1/common/errors" | ||||||
|  |  | ||||||
|  | type errPathObjHolder struct{} | ||||||
|  |  | ||||||
|  | func newError(values ...interface{}) *errors.Error { | ||||||
|  | 	return errors.New(values...).WithPathObj(errPathObjHolder{}) | ||||||
|  | } | ||||||
							
								
								
									
										143
									
								
								app/log/log.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								app/log/log.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,143 @@ | |||||||
|  | // +build !confonly | ||||||
|  |  | ||||||
|  | package log | ||||||
|  |  | ||||||
|  | //go:generate go run github.com/xtls/xray-core/v1/common/errors/errorgen | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"sync" | ||||||
|  |  | ||||||
|  | 	"github.com/xtls/xray-core/v1/common" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/log" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Instance is a log.Handler that handles logs. | ||||||
|  | type Instance struct { | ||||||
|  | 	sync.RWMutex | ||||||
|  | 	config       *Config | ||||||
|  | 	accessLogger log.Handler | ||||||
|  | 	errorLogger  log.Handler | ||||||
|  | 	active       bool | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // New creates a new log.Instance based on the given config. | ||||||
|  | func New(ctx context.Context, config *Config) (*Instance, error) { | ||||||
|  | 	g := &Instance{ | ||||||
|  | 		config: config, | ||||||
|  | 		active: false, | ||||||
|  | 	} | ||||||
|  | 	log.RegisterHandler(g) | ||||||
|  |  | ||||||
|  | 	// start logger instantly on inited | ||||||
|  | 	// other modules would log during init | ||||||
|  | 	if err := g.startInternal(); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	newError("Logger started").AtDebug().WriteToLog() | ||||||
|  | 	return g, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (g *Instance) initAccessLogger() error { | ||||||
|  | 	handler, err := createHandler(g.config.AccessLogType, HandlerCreatorOptions{ | ||||||
|  | 		Path: g.config.AccessLogPath, | ||||||
|  | 	}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	g.accessLogger = handler | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (g *Instance) initErrorLogger() error { | ||||||
|  | 	handler, err := createHandler(g.config.ErrorLogType, HandlerCreatorOptions{ | ||||||
|  | 		Path: g.config.ErrorLogPath, | ||||||
|  | 	}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	g.errorLogger = handler | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Type implements common.HasType. | ||||||
|  | func (*Instance) Type() interface{} { | ||||||
|  | 	return (*Instance)(nil) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (g *Instance) startInternal() error { | ||||||
|  | 	g.Lock() | ||||||
|  | 	defer g.Unlock() | ||||||
|  |  | ||||||
|  | 	if g.active { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	g.active = true | ||||||
|  |  | ||||||
|  | 	if err := g.initAccessLogger(); err != nil { | ||||||
|  | 		return newError("failed to initialize access logger").Base(err).AtWarning() | ||||||
|  | 	} | ||||||
|  | 	if err := g.initErrorLogger(); err != nil { | ||||||
|  | 		return newError("failed to initialize error logger").Base(err).AtWarning() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Start implements common.Runnable.Start(). | ||||||
|  | func (g *Instance) Start() error { | ||||||
|  | 	return g.startInternal() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Handle implements log.Handler. | ||||||
|  | func (g *Instance) Handle(msg log.Message) { | ||||||
|  | 	g.RLock() | ||||||
|  | 	defer g.RUnlock() | ||||||
|  |  | ||||||
|  | 	if !g.active { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	switch msg := msg.(type) { | ||||||
|  | 	case *log.AccessMessage: | ||||||
|  | 		if g.accessLogger != nil { | ||||||
|  | 			g.accessLogger.Handle(msg) | ||||||
|  | 		} | ||||||
|  | 	case *log.GeneralMessage: | ||||||
|  | 		if g.errorLogger != nil && msg.Severity <= g.config.ErrorLogLevel { | ||||||
|  | 			g.errorLogger.Handle(msg) | ||||||
|  | 		} | ||||||
|  | 	default: | ||||||
|  | 		// Swallow | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Close implements common.Closable.Close(). | ||||||
|  | func (g *Instance) Close() error { | ||||||
|  | 	newError("Logger closing").AtDebug().WriteToLog() | ||||||
|  |  | ||||||
|  | 	g.Lock() | ||||||
|  | 	defer g.Unlock() | ||||||
|  |  | ||||||
|  | 	if !g.active { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	g.active = false | ||||||
|  |  | ||||||
|  | 	common.Close(g.accessLogger) | ||||||
|  | 	g.accessLogger = nil | ||||||
|  |  | ||||||
|  | 	common.Close(g.errorLogger) | ||||||
|  | 	g.errorLogger = nil | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func init() { | ||||||
|  | 	common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { | ||||||
|  | 		return New(ctx, config.(*Config)) | ||||||
|  | 	})) | ||||||
|  | } | ||||||
							
								
								
									
										53
									
								
								app/log/log_creator.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								app/log/log_creator.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | |||||||
|  | // +build !confonly | ||||||
|  |  | ||||||
|  | package log | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"github.com/xtls/xray-core/v1/common" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/log" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type HandlerCreatorOptions struct { | ||||||
|  | 	Path string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type HandlerCreator func(LogType, HandlerCreatorOptions) (log.Handler, error) | ||||||
|  |  | ||||||
|  | var ( | ||||||
|  | 	handlerCreatorMap = make(map[LogType]HandlerCreator) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func RegisterHandlerCreator(logType LogType, f HandlerCreator) error { | ||||||
|  | 	if f == nil { | ||||||
|  | 		return newError("nil HandlerCreator") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	handlerCreatorMap[logType] = f | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func createHandler(logType LogType, options HandlerCreatorOptions) (log.Handler, error) { | ||||||
|  | 	creator, found := handlerCreatorMap[logType] | ||||||
|  | 	if !found { | ||||||
|  | 		return nil, newError("unable to create log handler for ", logType) | ||||||
|  | 	} | ||||||
|  | 	return creator(logType, options) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func init() { | ||||||
|  | 	common.Must(RegisterHandlerCreator(LogType_Console, func(lt LogType, options HandlerCreatorOptions) (log.Handler, error) { | ||||||
|  | 		return log.NewLogger(log.CreateStdoutLogWriter()), nil | ||||||
|  | 	})) | ||||||
|  |  | ||||||
|  | 	common.Must(RegisterHandlerCreator(LogType_File, func(lt LogType, options HandlerCreatorOptions) (log.Handler, error) { | ||||||
|  | 		creator, err := log.CreateFileLogWriter(options.Path) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		return log.NewLogger(creator), nil | ||||||
|  | 	})) | ||||||
|  |  | ||||||
|  | 	common.Must(RegisterHandlerCreator(LogType_None, func(lt LogType, options HandlerCreatorOptions) (log.Handler, error) { | ||||||
|  | 		return nil, nil | ||||||
|  | 	})) | ||||||
|  | } | ||||||
							
								
								
									
										52
									
								
								app/log/log_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								app/log/log_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | |||||||
|  | package log_test | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"github.com/golang/mock/gomock" | ||||||
|  | 	"github.com/xtls/xray-core/v1/app/log" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common" | ||||||
|  | 	clog "github.com/xtls/xray-core/v1/common/log" | ||||||
|  | 	"github.com/xtls/xray-core/v1/testing/mocks" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestCustomLogHandler(t *testing.T) { | ||||||
|  | 	mockCtl := gomock.NewController(t) | ||||||
|  | 	defer mockCtl.Finish() | ||||||
|  |  | ||||||
|  | 	var loggedValue []string | ||||||
|  |  | ||||||
|  | 	mockHandler := mocks.NewLogHandler(mockCtl) | ||||||
|  | 	mockHandler.EXPECT().Handle(gomock.Any()).AnyTimes().DoAndReturn(func(msg clog.Message) { | ||||||
|  | 		loggedValue = append(loggedValue, msg.String()) | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	log.RegisterHandlerCreator(log.LogType_Console, func(lt log.LogType, options log.HandlerCreatorOptions) (clog.Handler, error) { | ||||||
|  | 		return mockHandler, nil | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	logger, err := log.New(context.Background(), &log.Config{ | ||||||
|  | 		ErrorLogLevel: clog.Severity_Debug, | ||||||
|  | 		ErrorLogType:  log.LogType_Console, | ||||||
|  | 		AccessLogType: log.LogType_None, | ||||||
|  | 	}) | ||||||
|  | 	common.Must(err) | ||||||
|  |  | ||||||
|  | 	common.Must(logger.Start()) | ||||||
|  |  | ||||||
|  | 	clog.Record(&clog.GeneralMessage{ | ||||||
|  | 		Severity: clog.Severity_Debug, | ||||||
|  | 		Content:  "test", | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	if len(loggedValue) < 2 { | ||||||
|  | 		t.Fatal("expected 2 log messages, but actually ", loggedValue) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if loggedValue[1] != "[Debug] test" { | ||||||
|  | 		t.Fatal("expected '[Debug] test', but actually ", loggedValue[1]) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	common.Must(logger.Close()) | ||||||
|  | } | ||||||
							
								
								
									
										93
									
								
								app/policy/config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								app/policy/config.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,93 @@ | |||||||
|  | package policy | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/xtls/xray-core/v1/features/policy" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Duration converts Second to time.Duration. | ||||||
|  | func (s *Second) Duration() time.Duration { | ||||||
|  | 	if s == nil { | ||||||
|  | 		return 0 | ||||||
|  | 	} | ||||||
|  | 	return time.Second * time.Duration(s.Value) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func defaultPolicy() *Policy { | ||||||
|  | 	p := policy.SessionDefault() | ||||||
|  |  | ||||||
|  | 	return &Policy{ | ||||||
|  | 		Timeout: &Policy_Timeout{ | ||||||
|  | 			Handshake:      &Second{Value: uint32(p.Timeouts.Handshake / time.Second)}, | ||||||
|  | 			ConnectionIdle: &Second{Value: uint32(p.Timeouts.ConnectionIdle / time.Second)}, | ||||||
|  | 			UplinkOnly:     &Second{Value: uint32(p.Timeouts.UplinkOnly / time.Second)}, | ||||||
|  | 			DownlinkOnly:   &Second{Value: uint32(p.Timeouts.DownlinkOnly / time.Second)}, | ||||||
|  | 		}, | ||||||
|  | 		Buffer: &Policy_Buffer{ | ||||||
|  | 			Connection: p.Buffer.PerConnection, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (p *Policy_Timeout) overrideWith(another *Policy_Timeout) { | ||||||
|  | 	if another.Handshake != nil { | ||||||
|  | 		p.Handshake = &Second{Value: another.Handshake.Value} | ||||||
|  | 	} | ||||||
|  | 	if another.ConnectionIdle != nil { | ||||||
|  | 		p.ConnectionIdle = &Second{Value: another.ConnectionIdle.Value} | ||||||
|  | 	} | ||||||
|  | 	if another.UplinkOnly != nil { | ||||||
|  | 		p.UplinkOnly = &Second{Value: another.UplinkOnly.Value} | ||||||
|  | 	} | ||||||
|  | 	if another.DownlinkOnly != nil { | ||||||
|  | 		p.DownlinkOnly = &Second{Value: another.DownlinkOnly.Value} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (p *Policy) overrideWith(another *Policy) { | ||||||
|  | 	if another.Timeout != nil { | ||||||
|  | 		p.Timeout.overrideWith(another.Timeout) | ||||||
|  | 	} | ||||||
|  | 	if another.Stats != nil && p.Stats == nil { | ||||||
|  | 		p.Stats = &Policy_Stats{} | ||||||
|  | 		p.Stats = another.Stats | ||||||
|  | 	} | ||||||
|  | 	if another.Buffer != nil { | ||||||
|  | 		p.Buffer = &Policy_Buffer{ | ||||||
|  | 			Connection: another.Buffer.Connection, | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ToCorePolicy converts this Policy to policy.Session. | ||||||
|  | func (p *Policy) ToCorePolicy() policy.Session { | ||||||
|  | 	cp := policy.SessionDefault() | ||||||
|  |  | ||||||
|  | 	if p.Timeout != nil { | ||||||
|  | 		cp.Timeouts.ConnectionIdle = p.Timeout.ConnectionIdle.Duration() | ||||||
|  | 		cp.Timeouts.Handshake = p.Timeout.Handshake.Duration() | ||||||
|  | 		cp.Timeouts.DownlinkOnly = p.Timeout.DownlinkOnly.Duration() | ||||||
|  | 		cp.Timeouts.UplinkOnly = p.Timeout.UplinkOnly.Duration() | ||||||
|  | 	} | ||||||
|  | 	if p.Stats != nil { | ||||||
|  | 		cp.Stats.UserUplink = p.Stats.UserUplink | ||||||
|  | 		cp.Stats.UserDownlink = p.Stats.UserDownlink | ||||||
|  | 	} | ||||||
|  | 	if p.Buffer != nil { | ||||||
|  | 		cp.Buffer.PerConnection = p.Buffer.Connection | ||||||
|  | 	} | ||||||
|  | 	return cp | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ToCorePolicy converts this SystemPolicy to policy.System. | ||||||
|  | func (p *SystemPolicy) ToCorePolicy() policy.System { | ||||||
|  | 	return policy.System{ | ||||||
|  | 		Stats: policy.SystemStats{ | ||||||
|  | 			InboundUplink:    p.Stats.InboundUplink, | ||||||
|  | 			InboundDownlink:  p.Stats.InboundDownlink, | ||||||
|  | 			OutboundUplink:   p.Stats.OutboundUplink, | ||||||
|  | 			OutboundDownlink: p.Stats.OutboundDownlink, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										729
									
								
								app/policy/config.pb.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										729
									
								
								app/policy/config.pb.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,729 @@ | |||||||
|  | // Code generated by protoc-gen-go. DO NOT EDIT. | ||||||
|  | // versions: | ||||||
|  | // 	protoc-gen-go v1.25.0 | ||||||
|  | // 	protoc        v3.14.0 | ||||||
|  | // source: app/policy/config.proto | ||||||
|  |  | ||||||
|  | package policy | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	proto "github.com/golang/protobuf/proto" | ||||||
|  | 	protoreflect "google.golang.org/protobuf/reflect/protoreflect" | ||||||
|  | 	protoimpl "google.golang.org/protobuf/runtime/protoimpl" | ||||||
|  | 	reflect "reflect" | ||||||
|  | 	sync "sync" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	// Verify that this generated code is sufficiently up-to-date. | ||||||
|  | 	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) | ||||||
|  | 	// Verify that runtime/protoimpl is sufficiently up-to-date. | ||||||
|  | 	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // This is a compile-time assertion that a sufficiently up-to-date version | ||||||
|  | // of the legacy proto package is being used. | ||||||
|  | const _ = proto.ProtoPackageIsVersion4 | ||||||
|  |  | ||||||
|  | type Second struct { | ||||||
|  | 	state         protoimpl.MessageState | ||||||
|  | 	sizeCache     protoimpl.SizeCache | ||||||
|  | 	unknownFields protoimpl.UnknownFields | ||||||
|  |  | ||||||
|  | 	Value uint32 `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Second) Reset() { | ||||||
|  | 	*x = Second{} | ||||||
|  | 	if protoimpl.UnsafeEnabled { | ||||||
|  | 		mi := &file_app_policy_config_proto_msgTypes[0] | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		ms.StoreMessageInfo(mi) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Second) String() string { | ||||||
|  | 	return protoimpl.X.MessageStringOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (*Second) ProtoMessage() {} | ||||||
|  |  | ||||||
|  | func (x *Second) ProtoReflect() protoreflect.Message { | ||||||
|  | 	mi := &file_app_policy_config_proto_msgTypes[0] | ||||||
|  | 	if protoimpl.UnsafeEnabled && x != nil { | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		if ms.LoadMessageInfo() == nil { | ||||||
|  | 			ms.StoreMessageInfo(mi) | ||||||
|  | 		} | ||||||
|  | 		return ms | ||||||
|  | 	} | ||||||
|  | 	return mi.MessageOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Deprecated: Use Second.ProtoReflect.Descriptor instead. | ||||||
|  | func (*Second) Descriptor() ([]byte, []int) { | ||||||
|  | 	return file_app_policy_config_proto_rawDescGZIP(), []int{0} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Second) GetValue() uint32 { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Value | ||||||
|  | 	} | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Policy struct { | ||||||
|  | 	state         protoimpl.MessageState | ||||||
|  | 	sizeCache     protoimpl.SizeCache | ||||||
|  | 	unknownFields protoimpl.UnknownFields | ||||||
|  |  | ||||||
|  | 	Timeout *Policy_Timeout `protobuf:"bytes,1,opt,name=timeout,proto3" json:"timeout,omitempty"` | ||||||
|  | 	Stats   *Policy_Stats   `protobuf:"bytes,2,opt,name=stats,proto3" json:"stats,omitempty"` | ||||||
|  | 	Buffer  *Policy_Buffer  `protobuf:"bytes,3,opt,name=buffer,proto3" json:"buffer,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Policy) Reset() { | ||||||
|  | 	*x = Policy{} | ||||||
|  | 	if protoimpl.UnsafeEnabled { | ||||||
|  | 		mi := &file_app_policy_config_proto_msgTypes[1] | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		ms.StoreMessageInfo(mi) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Policy) String() string { | ||||||
|  | 	return protoimpl.X.MessageStringOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (*Policy) ProtoMessage() {} | ||||||
|  |  | ||||||
|  | func (x *Policy) ProtoReflect() protoreflect.Message { | ||||||
|  | 	mi := &file_app_policy_config_proto_msgTypes[1] | ||||||
|  | 	if protoimpl.UnsafeEnabled && x != nil { | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		if ms.LoadMessageInfo() == nil { | ||||||
|  | 			ms.StoreMessageInfo(mi) | ||||||
|  | 		} | ||||||
|  | 		return ms | ||||||
|  | 	} | ||||||
|  | 	return mi.MessageOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Deprecated: Use Policy.ProtoReflect.Descriptor instead. | ||||||
|  | func (*Policy) Descriptor() ([]byte, []int) { | ||||||
|  | 	return file_app_policy_config_proto_rawDescGZIP(), []int{1} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Policy) GetTimeout() *Policy_Timeout { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Timeout | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Policy) GetStats() *Policy_Stats { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Stats | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Policy) GetBuffer() *Policy_Buffer { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Buffer | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type SystemPolicy struct { | ||||||
|  | 	state         protoimpl.MessageState | ||||||
|  | 	sizeCache     protoimpl.SizeCache | ||||||
|  | 	unknownFields protoimpl.UnknownFields | ||||||
|  |  | ||||||
|  | 	Stats *SystemPolicy_Stats `protobuf:"bytes,1,opt,name=stats,proto3" json:"stats,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *SystemPolicy) Reset() { | ||||||
|  | 	*x = SystemPolicy{} | ||||||
|  | 	if protoimpl.UnsafeEnabled { | ||||||
|  | 		mi := &file_app_policy_config_proto_msgTypes[2] | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		ms.StoreMessageInfo(mi) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *SystemPolicy) String() string { | ||||||
|  | 	return protoimpl.X.MessageStringOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (*SystemPolicy) ProtoMessage() {} | ||||||
|  |  | ||||||
|  | func (x *SystemPolicy) ProtoReflect() protoreflect.Message { | ||||||
|  | 	mi := &file_app_policy_config_proto_msgTypes[2] | ||||||
|  | 	if protoimpl.UnsafeEnabled && x != nil { | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		if ms.LoadMessageInfo() == nil { | ||||||
|  | 			ms.StoreMessageInfo(mi) | ||||||
|  | 		} | ||||||
|  | 		return ms | ||||||
|  | 	} | ||||||
|  | 	return mi.MessageOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Deprecated: Use SystemPolicy.ProtoReflect.Descriptor instead. | ||||||
|  | func (*SystemPolicy) Descriptor() ([]byte, []int) { | ||||||
|  | 	return file_app_policy_config_proto_rawDescGZIP(), []int{2} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *SystemPolicy) GetStats() *SystemPolicy_Stats { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Stats | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Config struct { | ||||||
|  | 	state         protoimpl.MessageState | ||||||
|  | 	sizeCache     protoimpl.SizeCache | ||||||
|  | 	unknownFields protoimpl.UnknownFields | ||||||
|  |  | ||||||
|  | 	Level  map[uint32]*Policy `protobuf:"bytes,1,rep,name=level,proto3" json:"level,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` | ||||||
|  | 	System *SystemPolicy      `protobuf:"bytes,2,opt,name=system,proto3" json:"system,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Config) Reset() { | ||||||
|  | 	*x = Config{} | ||||||
|  | 	if protoimpl.UnsafeEnabled { | ||||||
|  | 		mi := &file_app_policy_config_proto_msgTypes[3] | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		ms.StoreMessageInfo(mi) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Config) String() string { | ||||||
|  | 	return protoimpl.X.MessageStringOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (*Config) ProtoMessage() {} | ||||||
|  |  | ||||||
|  | func (x *Config) ProtoReflect() protoreflect.Message { | ||||||
|  | 	mi := &file_app_policy_config_proto_msgTypes[3] | ||||||
|  | 	if protoimpl.UnsafeEnabled && x != nil { | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		if ms.LoadMessageInfo() == nil { | ||||||
|  | 			ms.StoreMessageInfo(mi) | ||||||
|  | 		} | ||||||
|  | 		return ms | ||||||
|  | 	} | ||||||
|  | 	return mi.MessageOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Deprecated: Use Config.ProtoReflect.Descriptor instead. | ||||||
|  | func (*Config) Descriptor() ([]byte, []int) { | ||||||
|  | 	return file_app_policy_config_proto_rawDescGZIP(), []int{3} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Config) GetLevel() map[uint32]*Policy { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Level | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Config) GetSystem() *SystemPolicy { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.System | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Timeout is a message for timeout settings in various stages, in seconds. | ||||||
|  | type Policy_Timeout struct { | ||||||
|  | 	state         protoimpl.MessageState | ||||||
|  | 	sizeCache     protoimpl.SizeCache | ||||||
|  | 	unknownFields protoimpl.UnknownFields | ||||||
|  |  | ||||||
|  | 	Handshake      *Second `protobuf:"bytes,1,opt,name=handshake,proto3" json:"handshake,omitempty"` | ||||||
|  | 	ConnectionIdle *Second `protobuf:"bytes,2,opt,name=connection_idle,json=connectionIdle,proto3" json:"connection_idle,omitempty"` | ||||||
|  | 	UplinkOnly     *Second `protobuf:"bytes,3,opt,name=uplink_only,json=uplinkOnly,proto3" json:"uplink_only,omitempty"` | ||||||
|  | 	DownlinkOnly   *Second `protobuf:"bytes,4,opt,name=downlink_only,json=downlinkOnly,proto3" json:"downlink_only,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Policy_Timeout) Reset() { | ||||||
|  | 	*x = Policy_Timeout{} | ||||||
|  | 	if protoimpl.UnsafeEnabled { | ||||||
|  | 		mi := &file_app_policy_config_proto_msgTypes[4] | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		ms.StoreMessageInfo(mi) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Policy_Timeout) String() string { | ||||||
|  | 	return protoimpl.X.MessageStringOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (*Policy_Timeout) ProtoMessage() {} | ||||||
|  |  | ||||||
|  | func (x *Policy_Timeout) ProtoReflect() protoreflect.Message { | ||||||
|  | 	mi := &file_app_policy_config_proto_msgTypes[4] | ||||||
|  | 	if protoimpl.UnsafeEnabled && x != nil { | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		if ms.LoadMessageInfo() == nil { | ||||||
|  | 			ms.StoreMessageInfo(mi) | ||||||
|  | 		} | ||||||
|  | 		return ms | ||||||
|  | 	} | ||||||
|  | 	return mi.MessageOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Deprecated: Use Policy_Timeout.ProtoReflect.Descriptor instead. | ||||||
|  | func (*Policy_Timeout) Descriptor() ([]byte, []int) { | ||||||
|  | 	return file_app_policy_config_proto_rawDescGZIP(), []int{1, 0} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Policy_Timeout) GetHandshake() *Second { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Handshake | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Policy_Timeout) GetConnectionIdle() *Second { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.ConnectionIdle | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Policy_Timeout) GetUplinkOnly() *Second { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.UplinkOnly | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Policy_Timeout) GetDownlinkOnly() *Second { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.DownlinkOnly | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Policy_Stats struct { | ||||||
|  | 	state         protoimpl.MessageState | ||||||
|  | 	sizeCache     protoimpl.SizeCache | ||||||
|  | 	unknownFields protoimpl.UnknownFields | ||||||
|  |  | ||||||
|  | 	UserUplink   bool `protobuf:"varint,1,opt,name=user_uplink,json=userUplink,proto3" json:"user_uplink,omitempty"` | ||||||
|  | 	UserDownlink bool `protobuf:"varint,2,opt,name=user_downlink,json=userDownlink,proto3" json:"user_downlink,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Policy_Stats) Reset() { | ||||||
|  | 	*x = Policy_Stats{} | ||||||
|  | 	if protoimpl.UnsafeEnabled { | ||||||
|  | 		mi := &file_app_policy_config_proto_msgTypes[5] | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		ms.StoreMessageInfo(mi) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Policy_Stats) String() string { | ||||||
|  | 	return protoimpl.X.MessageStringOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (*Policy_Stats) ProtoMessage() {} | ||||||
|  |  | ||||||
|  | func (x *Policy_Stats) ProtoReflect() protoreflect.Message { | ||||||
|  | 	mi := &file_app_policy_config_proto_msgTypes[5] | ||||||
|  | 	if protoimpl.UnsafeEnabled && x != nil { | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		if ms.LoadMessageInfo() == nil { | ||||||
|  | 			ms.StoreMessageInfo(mi) | ||||||
|  | 		} | ||||||
|  | 		return ms | ||||||
|  | 	} | ||||||
|  | 	return mi.MessageOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Deprecated: Use Policy_Stats.ProtoReflect.Descriptor instead. | ||||||
|  | func (*Policy_Stats) Descriptor() ([]byte, []int) { | ||||||
|  | 	return file_app_policy_config_proto_rawDescGZIP(), []int{1, 1} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Policy_Stats) GetUserUplink() bool { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.UserUplink | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Policy_Stats) GetUserDownlink() bool { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.UserDownlink | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Policy_Buffer struct { | ||||||
|  | 	state         protoimpl.MessageState | ||||||
|  | 	sizeCache     protoimpl.SizeCache | ||||||
|  | 	unknownFields protoimpl.UnknownFields | ||||||
|  |  | ||||||
|  | 	// Buffer size per connection, in bytes. -1 for unlimited buffer. | ||||||
|  | 	Connection int32 `protobuf:"varint,1,opt,name=connection,proto3" json:"connection,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Policy_Buffer) Reset() { | ||||||
|  | 	*x = Policy_Buffer{} | ||||||
|  | 	if protoimpl.UnsafeEnabled { | ||||||
|  | 		mi := &file_app_policy_config_proto_msgTypes[6] | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		ms.StoreMessageInfo(mi) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Policy_Buffer) String() string { | ||||||
|  | 	return protoimpl.X.MessageStringOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (*Policy_Buffer) ProtoMessage() {} | ||||||
|  |  | ||||||
|  | func (x *Policy_Buffer) ProtoReflect() protoreflect.Message { | ||||||
|  | 	mi := &file_app_policy_config_proto_msgTypes[6] | ||||||
|  | 	if protoimpl.UnsafeEnabled && x != nil { | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		if ms.LoadMessageInfo() == nil { | ||||||
|  | 			ms.StoreMessageInfo(mi) | ||||||
|  | 		} | ||||||
|  | 		return ms | ||||||
|  | 	} | ||||||
|  | 	return mi.MessageOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Deprecated: Use Policy_Buffer.ProtoReflect.Descriptor instead. | ||||||
|  | func (*Policy_Buffer) Descriptor() ([]byte, []int) { | ||||||
|  | 	return file_app_policy_config_proto_rawDescGZIP(), []int{1, 2} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Policy_Buffer) GetConnection() int32 { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Connection | ||||||
|  | 	} | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type SystemPolicy_Stats struct { | ||||||
|  | 	state         protoimpl.MessageState | ||||||
|  | 	sizeCache     protoimpl.SizeCache | ||||||
|  | 	unknownFields protoimpl.UnknownFields | ||||||
|  |  | ||||||
|  | 	InboundUplink    bool `protobuf:"varint,1,opt,name=inbound_uplink,json=inboundUplink,proto3" json:"inbound_uplink,omitempty"` | ||||||
|  | 	InboundDownlink  bool `protobuf:"varint,2,opt,name=inbound_downlink,json=inboundDownlink,proto3" json:"inbound_downlink,omitempty"` | ||||||
|  | 	OutboundUplink   bool `protobuf:"varint,3,opt,name=outbound_uplink,json=outboundUplink,proto3" json:"outbound_uplink,omitempty"` | ||||||
|  | 	OutboundDownlink bool `protobuf:"varint,4,opt,name=outbound_downlink,json=outboundDownlink,proto3" json:"outbound_downlink,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *SystemPolicy_Stats) Reset() { | ||||||
|  | 	*x = SystemPolicy_Stats{} | ||||||
|  | 	if protoimpl.UnsafeEnabled { | ||||||
|  | 		mi := &file_app_policy_config_proto_msgTypes[7] | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		ms.StoreMessageInfo(mi) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *SystemPolicy_Stats) String() string { | ||||||
|  | 	return protoimpl.X.MessageStringOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (*SystemPolicy_Stats) ProtoMessage() {} | ||||||
|  |  | ||||||
|  | func (x *SystemPolicy_Stats) ProtoReflect() protoreflect.Message { | ||||||
|  | 	mi := &file_app_policy_config_proto_msgTypes[7] | ||||||
|  | 	if protoimpl.UnsafeEnabled && x != nil { | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		if ms.LoadMessageInfo() == nil { | ||||||
|  | 			ms.StoreMessageInfo(mi) | ||||||
|  | 		} | ||||||
|  | 		return ms | ||||||
|  | 	} | ||||||
|  | 	return mi.MessageOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Deprecated: Use SystemPolicy_Stats.ProtoReflect.Descriptor instead. | ||||||
|  | func (*SystemPolicy_Stats) Descriptor() ([]byte, []int) { | ||||||
|  | 	return file_app_policy_config_proto_rawDescGZIP(), []int{2, 0} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *SystemPolicy_Stats) GetInboundUplink() bool { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.InboundUplink | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *SystemPolicy_Stats) GetInboundDownlink() bool { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.InboundDownlink | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *SystemPolicy_Stats) GetOutboundUplink() bool { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.OutboundUplink | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *SystemPolicy_Stats) GetOutboundDownlink() bool { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.OutboundDownlink | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var File_app_policy_config_proto protoreflect.FileDescriptor | ||||||
|  |  | ||||||
|  | var file_app_policy_config_proto_rawDesc = []byte{ | ||||||
|  | 	0x0a, 0x17, 0x61, 0x70, 0x70, 0x2f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2f, 0x63, 0x6f, 0x6e, | ||||||
|  | 	0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x78, 0x72, 0x61, 0x79, 0x2e, | ||||||
|  | 	0x61, 0x70, 0x70, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x22, 0x1e, 0x0a, 0x06, 0x53, 0x65, | ||||||
|  | 	0x63, 0x6f, 0x6e, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, | ||||||
|  | 	0x01, 0x28, 0x0d, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0xa6, 0x04, 0x0a, 0x06, 0x50, | ||||||
|  | 	0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x39, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, | ||||||
|  | 	0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, | ||||||
|  | 	0x70, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, | ||||||
|  | 	0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, | ||||||
|  | 	0x12, 0x33, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, | ||||||
|  | 	0x1d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, | ||||||
|  | 	0x79, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x05, | ||||||
|  | 	0x73, 0x74, 0x61, 0x74, 0x73, 0x12, 0x36, 0x0a, 0x06, 0x62, 0x75, 0x66, 0x66, 0x65, 0x72, 0x18, | ||||||
|  | 	0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, | ||||||
|  | 	0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x42, | ||||||
|  | 	0x75, 0x66, 0x66, 0x65, 0x72, 0x52, 0x06, 0x62, 0x75, 0x66, 0x66, 0x65, 0x72, 0x1a, 0xfa, 0x01, | ||||||
|  | 	0x0a, 0x07, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x35, 0x0a, 0x09, 0x68, 0x61, 0x6e, | ||||||
|  | 	0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x78, | ||||||
|  | 	0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x53, | ||||||
|  | 	0x65, 0x63, 0x6f, 0x6e, 0x64, 0x52, 0x09, 0x68, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, | ||||||
|  | 	0x12, 0x40, 0x0a, 0x0f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, | ||||||
|  | 	0x64, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x78, 0x72, 0x61, 0x79, | ||||||
|  | 	0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x53, 0x65, 0x63, 0x6f, | ||||||
|  | 	0x6e, 0x64, 0x52, 0x0e, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, | ||||||
|  | 	0x6c, 0x65, 0x12, 0x38, 0x0a, 0x0b, 0x75, 0x70, 0x6c, 0x69, 0x6e, 0x6b, 0x5f, 0x6f, 0x6e, 0x6c, | ||||||
|  | 	0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, | ||||||
|  | 	0x70, 0x70, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, | ||||||
|  | 	0x52, 0x0a, 0x75, 0x70, 0x6c, 0x69, 0x6e, 0x6b, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x3c, 0x0a, 0x0d, | ||||||
|  | 	0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x69, 0x6e, 0x6b, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x04, 0x20, | ||||||
|  | 	0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, | ||||||
|  | 	0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x52, 0x0c, 0x64, 0x6f, | ||||||
|  | 	0x77, 0x6e, 0x6c, 0x69, 0x6e, 0x6b, 0x4f, 0x6e, 0x6c, 0x79, 0x1a, 0x4d, 0x0a, 0x05, 0x53, 0x74, | ||||||
|  | 	0x61, 0x74, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x75, 0x70, 0x6c, 0x69, | ||||||
|  | 	0x6e, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x55, 0x70, | ||||||
|  | 	0x6c, 0x69, 0x6e, 0x6b, 0x12, 0x23, 0x0a, 0x0d, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x64, 0x6f, 0x77, | ||||||
|  | 	0x6e, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x75, 0x73, 0x65, | ||||||
|  | 	0x72, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x69, 0x6e, 0x6b, 0x1a, 0x28, 0x0a, 0x06, 0x42, 0x75, 0x66, | ||||||
|  | 	0x66, 0x65, 0x72, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, | ||||||
|  | 	0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, | ||||||
|  | 	0x69, 0x6f, 0x6e, 0x22, 0xfb, 0x01, 0x0a, 0x0c, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x50, 0x6f, | ||||||
|  | 	0x6c, 0x69, 0x63, 0x79, 0x12, 0x39, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x01, 0x20, | ||||||
|  | 	0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, | ||||||
|  | 	0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x50, 0x6f, 0x6c, 0x69, | ||||||
|  | 	0x63, 0x79, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x1a, | ||||||
|  | 	0xaf, 0x01, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x69, 0x6e, 0x62, | ||||||
|  | 	0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x75, 0x70, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, | ||||||
|  | 	0x08, 0x52, 0x0d, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x55, 0x70, 0x6c, 0x69, 0x6e, 0x6b, | ||||||
|  | 	0x12, 0x29, 0x0a, 0x10, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x64, 0x6f, 0x77, 0x6e, | ||||||
|  | 	0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x69, 0x6e, 0x62, 0x6f, | ||||||
|  | 	0x75, 0x6e, 0x64, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x69, 0x6e, 0x6b, 0x12, 0x27, 0x0a, 0x0f, 0x6f, | ||||||
|  | 	0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x75, 0x70, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x03, | ||||||
|  | 	0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x55, 0x70, | ||||||
|  | 	0x6c, 0x69, 0x6e, 0x6b, 0x12, 0x2b, 0x0a, 0x11, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, | ||||||
|  | 	0x5f, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, | ||||||
|  | 	0x10, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x69, 0x6e, | ||||||
|  | 	0x6b, 0x22, 0xcc, 0x01, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x38, 0x0a, 0x05, | ||||||
|  | 	0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x78, 0x72, | ||||||
|  | 	0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x43, 0x6f, | ||||||
|  | 	0x6e, 0x66, 0x69, 0x67, 0x2e, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, | ||||||
|  | 	0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x35, 0x0a, 0x06, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, | ||||||
|  | 	0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, | ||||||
|  | 	0x70, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x50, | ||||||
|  | 	0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x06, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x1a, 0x51, 0x0a, | ||||||
|  | 	0x0a, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, | ||||||
|  | 	0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2d, 0x0a, | ||||||
|  | 	0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x78, | ||||||
|  | 	0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x50, | ||||||
|  | 	0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, | ||||||
|  | 	0x42, 0x52, 0x0a, 0x13, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, | ||||||
|  | 	0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x50, 0x01, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, | ||||||
|  | 	0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, | ||||||
|  | 	0x63, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x70, 0x6f, 0x6c, 0x69, | ||||||
|  | 	0x63, 0x79, 0xaa, 0x02, 0x0f, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x50, 0x6f, | ||||||
|  | 	0x6c, 0x69, 0x63, 0x79, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var ( | ||||||
|  | 	file_app_policy_config_proto_rawDescOnce sync.Once | ||||||
|  | 	file_app_policy_config_proto_rawDescData = file_app_policy_config_proto_rawDesc | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func file_app_policy_config_proto_rawDescGZIP() []byte { | ||||||
|  | 	file_app_policy_config_proto_rawDescOnce.Do(func() { | ||||||
|  | 		file_app_policy_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_app_policy_config_proto_rawDescData) | ||||||
|  | 	}) | ||||||
|  | 	return file_app_policy_config_proto_rawDescData | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var file_app_policy_config_proto_msgTypes = make([]protoimpl.MessageInfo, 9) | ||||||
|  | var file_app_policy_config_proto_goTypes = []interface{}{ | ||||||
|  | 	(*Second)(nil),             // 0: xray.app.policy.Second | ||||||
|  | 	(*Policy)(nil),             // 1: xray.app.policy.Policy | ||||||
|  | 	(*SystemPolicy)(nil),       // 2: xray.app.policy.SystemPolicy | ||||||
|  | 	(*Config)(nil),             // 3: xray.app.policy.Config | ||||||
|  | 	(*Policy_Timeout)(nil),     // 4: xray.app.policy.Policy.Timeout | ||||||
|  | 	(*Policy_Stats)(nil),       // 5: xray.app.policy.Policy.Stats | ||||||
|  | 	(*Policy_Buffer)(nil),      // 6: xray.app.policy.Policy.Buffer | ||||||
|  | 	(*SystemPolicy_Stats)(nil), // 7: xray.app.policy.SystemPolicy.Stats | ||||||
|  | 	nil,                        // 8: xray.app.policy.Config.LevelEntry | ||||||
|  | } | ||||||
|  | var file_app_policy_config_proto_depIdxs = []int32{ | ||||||
|  | 	4,  // 0: xray.app.policy.Policy.timeout:type_name -> xray.app.policy.Policy.Timeout | ||||||
|  | 	5,  // 1: xray.app.policy.Policy.stats:type_name -> xray.app.policy.Policy.Stats | ||||||
|  | 	6,  // 2: xray.app.policy.Policy.buffer:type_name -> xray.app.policy.Policy.Buffer | ||||||
|  | 	7,  // 3: xray.app.policy.SystemPolicy.stats:type_name -> xray.app.policy.SystemPolicy.Stats | ||||||
|  | 	8,  // 4: xray.app.policy.Config.level:type_name -> xray.app.policy.Config.LevelEntry | ||||||
|  | 	2,  // 5: xray.app.policy.Config.system:type_name -> xray.app.policy.SystemPolicy | ||||||
|  | 	0,  // 6: xray.app.policy.Policy.Timeout.handshake:type_name -> xray.app.policy.Second | ||||||
|  | 	0,  // 7: xray.app.policy.Policy.Timeout.connection_idle:type_name -> xray.app.policy.Second | ||||||
|  | 	0,  // 8: xray.app.policy.Policy.Timeout.uplink_only:type_name -> xray.app.policy.Second | ||||||
|  | 	0,  // 9: xray.app.policy.Policy.Timeout.downlink_only:type_name -> xray.app.policy.Second | ||||||
|  | 	1,  // 10: xray.app.policy.Config.LevelEntry.value:type_name -> xray.app.policy.Policy | ||||||
|  | 	11, // [11:11] is the sub-list for method output_type | ||||||
|  | 	11, // [11:11] is the sub-list for method input_type | ||||||
|  | 	11, // [11:11] is the sub-list for extension type_name | ||||||
|  | 	11, // [11:11] is the sub-list for extension extendee | ||||||
|  | 	0,  // [0:11] is the sub-list for field type_name | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func init() { file_app_policy_config_proto_init() } | ||||||
|  | func file_app_policy_config_proto_init() { | ||||||
|  | 	if File_app_policy_config_proto != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if !protoimpl.UnsafeEnabled { | ||||||
|  | 		file_app_policy_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { | ||||||
|  | 			switch v := v.(*Second); i { | ||||||
|  | 			case 0: | ||||||
|  | 				return &v.state | ||||||
|  | 			case 1: | ||||||
|  | 				return &v.sizeCache | ||||||
|  | 			case 2: | ||||||
|  | 				return &v.unknownFields | ||||||
|  | 			default: | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		file_app_policy_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { | ||||||
|  | 			switch v := v.(*Policy); i { | ||||||
|  | 			case 0: | ||||||
|  | 				return &v.state | ||||||
|  | 			case 1: | ||||||
|  | 				return &v.sizeCache | ||||||
|  | 			case 2: | ||||||
|  | 				return &v.unknownFields | ||||||
|  | 			default: | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		file_app_policy_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { | ||||||
|  | 			switch v := v.(*SystemPolicy); i { | ||||||
|  | 			case 0: | ||||||
|  | 				return &v.state | ||||||
|  | 			case 1: | ||||||
|  | 				return &v.sizeCache | ||||||
|  | 			case 2: | ||||||
|  | 				return &v.unknownFields | ||||||
|  | 			default: | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		file_app_policy_config_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { | ||||||
|  | 			switch v := v.(*Config); i { | ||||||
|  | 			case 0: | ||||||
|  | 				return &v.state | ||||||
|  | 			case 1: | ||||||
|  | 				return &v.sizeCache | ||||||
|  | 			case 2: | ||||||
|  | 				return &v.unknownFields | ||||||
|  | 			default: | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		file_app_policy_config_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { | ||||||
|  | 			switch v := v.(*Policy_Timeout); i { | ||||||
|  | 			case 0: | ||||||
|  | 				return &v.state | ||||||
|  | 			case 1: | ||||||
|  | 				return &v.sizeCache | ||||||
|  | 			case 2: | ||||||
|  | 				return &v.unknownFields | ||||||
|  | 			default: | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		file_app_policy_config_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { | ||||||
|  | 			switch v := v.(*Policy_Stats); i { | ||||||
|  | 			case 0: | ||||||
|  | 				return &v.state | ||||||
|  | 			case 1: | ||||||
|  | 				return &v.sizeCache | ||||||
|  | 			case 2: | ||||||
|  | 				return &v.unknownFields | ||||||
|  | 			default: | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		file_app_policy_config_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { | ||||||
|  | 			switch v := v.(*Policy_Buffer); i { | ||||||
|  | 			case 0: | ||||||
|  | 				return &v.state | ||||||
|  | 			case 1: | ||||||
|  | 				return &v.sizeCache | ||||||
|  | 			case 2: | ||||||
|  | 				return &v.unknownFields | ||||||
|  | 			default: | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		file_app_policy_config_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { | ||||||
|  | 			switch v := v.(*SystemPolicy_Stats); i { | ||||||
|  | 			case 0: | ||||||
|  | 				return &v.state | ||||||
|  | 			case 1: | ||||||
|  | 				return &v.sizeCache | ||||||
|  | 			case 2: | ||||||
|  | 				return &v.unknownFields | ||||||
|  | 			default: | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	type x struct{} | ||||||
|  | 	out := protoimpl.TypeBuilder{ | ||||||
|  | 		File: protoimpl.DescBuilder{ | ||||||
|  | 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(), | ||||||
|  | 			RawDescriptor: file_app_policy_config_proto_rawDesc, | ||||||
|  | 			NumEnums:      0, | ||||||
|  | 			NumMessages:   9, | ||||||
|  | 			NumExtensions: 0, | ||||||
|  | 			NumServices:   0, | ||||||
|  | 		}, | ||||||
|  | 		GoTypes:           file_app_policy_config_proto_goTypes, | ||||||
|  | 		DependencyIndexes: file_app_policy_config_proto_depIdxs, | ||||||
|  | 		MessageInfos:      file_app_policy_config_proto_msgTypes, | ||||||
|  | 	}.Build() | ||||||
|  | 	File_app_policy_config_proto = out.File | ||||||
|  | 	file_app_policy_config_proto_rawDesc = nil | ||||||
|  | 	file_app_policy_config_proto_goTypes = nil | ||||||
|  | 	file_app_policy_config_proto_depIdxs = nil | ||||||
|  | } | ||||||
							
								
								
									
										51
									
								
								app/policy/config.proto
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								app/policy/config.proto
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | |||||||
|  | syntax = "proto3"; | ||||||
|  |  | ||||||
|  | package xray.app.policy; | ||||||
|  | option csharp_namespace = "Xray.App.Policy"; | ||||||
|  | option go_package = "github.com/xtls/xray-core/v1/app/policy"; | ||||||
|  | option java_package = "com.xray.app.policy"; | ||||||
|  | option java_multiple_files = true; | ||||||
|  |  | ||||||
|  | message Second { | ||||||
|  |   uint32 value = 1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message Policy { | ||||||
|  |   // Timeout is a message for timeout settings in various stages, in seconds. | ||||||
|  |   message Timeout { | ||||||
|  |     Second handshake = 1; | ||||||
|  |     Second connection_idle = 2; | ||||||
|  |     Second uplink_only = 3; | ||||||
|  |     Second downlink_only = 4; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   message Stats { | ||||||
|  |     bool user_uplink = 1; | ||||||
|  |     bool user_downlink = 2; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   message Buffer { | ||||||
|  |     // Buffer size per connection, in bytes. -1 for unlimited buffer. | ||||||
|  |     int32 connection = 1; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Timeout timeout = 1; | ||||||
|  |   Stats stats = 2; | ||||||
|  |   Buffer buffer = 3; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message SystemPolicy { | ||||||
|  |   message Stats { | ||||||
|  |     bool inbound_uplink = 1; | ||||||
|  |     bool inbound_downlink = 2; | ||||||
|  |     bool outbound_uplink = 3; | ||||||
|  |     bool outbound_downlink = 4; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Stats stats = 1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message Config { | ||||||
|  |   map<uint32, Policy> level = 1; | ||||||
|  |   SystemPolicy system = 2; | ||||||
|  | } | ||||||
							
								
								
									
										9
									
								
								app/policy/errors.generated.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								app/policy/errors.generated.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | package policy | ||||||
|  |  | ||||||
|  | import "github.com/xtls/xray-core/v1/common/errors" | ||||||
|  |  | ||||||
|  | type errPathObjHolder struct{} | ||||||
|  |  | ||||||
|  | func newError(values ...interface{}) *errors.Error { | ||||||
|  | 	return errors.New(values...).WithPathObj(errPathObjHolder{}) | ||||||
|  | } | ||||||
							
								
								
									
										68
									
								
								app/policy/manager.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								app/policy/manager.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,68 @@ | |||||||
|  | package policy | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  |  | ||||||
|  | 	"github.com/xtls/xray-core/v1/common" | ||||||
|  | 	"github.com/xtls/xray-core/v1/features/policy" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Instance is an instance of Policy manager. | ||||||
|  | type Instance struct { | ||||||
|  | 	levels map[uint32]*Policy | ||||||
|  | 	system *SystemPolicy | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // New creates new Policy manager instance. | ||||||
|  | func New(ctx context.Context, config *Config) (*Instance, error) { | ||||||
|  | 	m := &Instance{ | ||||||
|  | 		levels: make(map[uint32]*Policy), | ||||||
|  | 		system: config.System, | ||||||
|  | 	} | ||||||
|  | 	if len(config.Level) > 0 { | ||||||
|  | 		for lv, p := range config.Level { | ||||||
|  | 			pp := defaultPolicy() | ||||||
|  | 			pp.overrideWith(p) | ||||||
|  | 			m.levels[lv] = pp | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return m, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Type implements common.HasType. | ||||||
|  | func (*Instance) Type() interface{} { | ||||||
|  | 	return policy.ManagerType() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ForLevel implements policy.Manager. | ||||||
|  | func (m *Instance) ForLevel(level uint32) policy.Session { | ||||||
|  | 	if p, ok := m.levels[level]; ok { | ||||||
|  | 		return p.ToCorePolicy() | ||||||
|  | 	} | ||||||
|  | 	return policy.SessionDefault() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ForSystem implements policy.Manager. | ||||||
|  | func (m *Instance) ForSystem() policy.System { | ||||||
|  | 	if m.system == nil { | ||||||
|  | 		return policy.System{} | ||||||
|  | 	} | ||||||
|  | 	return m.system.ToCorePolicy() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Start implements common.Runnable.Start(). | ||||||
|  | func (m *Instance) Start() error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Close implements common.Closable.Close(). | ||||||
|  | func (m *Instance) Close() error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func init() { | ||||||
|  | 	common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { | ||||||
|  | 		return New(ctx, config.(*Config)) | ||||||
|  | 	})) | ||||||
|  | } | ||||||
							
								
								
									
										45
									
								
								app/policy/manager_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								app/policy/manager_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | |||||||
|  | package policy_test | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"testing" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	. "github.com/xtls/xray-core/v1/app/policy" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common" | ||||||
|  | 	"github.com/xtls/xray-core/v1/features/policy" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestPolicy(t *testing.T) { | ||||||
|  | 	manager, err := New(context.Background(), &Config{ | ||||||
|  | 		Level: map[uint32]*Policy{ | ||||||
|  | 			0: { | ||||||
|  | 				Timeout: &Policy_Timeout{ | ||||||
|  | 					Handshake: &Second{ | ||||||
|  | 						Value: 2, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	}) | ||||||
|  | 	common.Must(err) | ||||||
|  |  | ||||||
|  | 	pDefault := policy.SessionDefault() | ||||||
|  |  | ||||||
|  | 	{ | ||||||
|  | 		p := manager.ForLevel(0) | ||||||
|  | 		if p.Timeouts.Handshake != 2*time.Second { | ||||||
|  | 			t.Error("expect 2 sec timeout, but got ", p.Timeouts.Handshake) | ||||||
|  | 		} | ||||||
|  | 		if p.Timeouts.ConnectionIdle != pDefault.Timeouts.ConnectionIdle { | ||||||
|  | 			t.Error("expect ", pDefault.Timeouts.ConnectionIdle, " sec timeout, but got ", p.Timeouts.ConnectionIdle) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	{ | ||||||
|  | 		p := manager.ForLevel(1) | ||||||
|  | 		if p.Timeouts.Handshake != pDefault.Timeouts.Handshake { | ||||||
|  | 			t.Error("expect ", pDefault.Timeouts.Handshake, " sec timeout, but got ", p.Timeouts.Handshake) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										4
									
								
								app/policy/policy.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								app/policy/policy.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | |||||||
|  | // Package policy is an implementation of policy.Manager feature. | ||||||
|  | package policy | ||||||
|  |  | ||||||
|  | //go:generate go run github.com/xtls/xray-core/v1/common/errors/errorgen | ||||||
							
								
								
									
										150
									
								
								app/proxyman/command/command.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								app/proxyman/command/command.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,150 @@ | |||||||
|  | // +build !confonly | ||||||
|  |  | ||||||
|  | package command | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  |  | ||||||
|  | 	grpc "google.golang.org/grpc" | ||||||
|  |  | ||||||
|  | 	"github.com/xtls/xray-core/v1/common" | ||||||
|  | 	"github.com/xtls/xray-core/v1/core" | ||||||
|  | 	"github.com/xtls/xray-core/v1/features/inbound" | ||||||
|  | 	"github.com/xtls/xray-core/v1/features/outbound" | ||||||
|  | 	"github.com/xtls/xray-core/v1/proxy" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // InboundOperation is the interface for operations that applies to inbound handlers. | ||||||
|  | type InboundOperation interface { | ||||||
|  | 	// ApplyInbound applies this operation to the given inbound handler. | ||||||
|  | 	ApplyInbound(context.Context, inbound.Handler) error | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // OutboundOperation is the interface for operations that applies to outbound handlers. | ||||||
|  | type OutboundOperation interface { | ||||||
|  | 	// ApplyOutbound applies this operation to the given outbound handler. | ||||||
|  | 	ApplyOutbound(context.Context, outbound.Handler) error | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func getInbound(handler inbound.Handler) (proxy.Inbound, error) { | ||||||
|  | 	gi, ok := handler.(proxy.GetInbound) | ||||||
|  | 	if !ok { | ||||||
|  | 		return nil, newError("can't get inbound proxy from handler.") | ||||||
|  | 	} | ||||||
|  | 	return gi.GetInbound(), nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ApplyInbound implements InboundOperation. | ||||||
|  | func (op *AddUserOperation) ApplyInbound(ctx context.Context, handler inbound.Handler) error { | ||||||
|  | 	p, err := getInbound(handler) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	um, ok := p.(proxy.UserManager) | ||||||
|  | 	if !ok { | ||||||
|  | 		return newError("proxy is not a UserManager") | ||||||
|  | 	} | ||||||
|  | 	mUser, err := op.User.ToMemoryUser() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return newError("failed to parse user").Base(err) | ||||||
|  | 	} | ||||||
|  | 	return um.AddUser(ctx, mUser) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ApplyInbound implements InboundOperation. | ||||||
|  | func (op *RemoveUserOperation) ApplyInbound(ctx context.Context, handler inbound.Handler) error { | ||||||
|  | 	p, err := getInbound(handler) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	um, ok := p.(proxy.UserManager) | ||||||
|  | 	if !ok { | ||||||
|  | 		return newError("proxy is not a UserManager") | ||||||
|  | 	} | ||||||
|  | 	return um.RemoveUser(ctx, op.Email) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type handlerServer struct { | ||||||
|  | 	s   *core.Instance | ||||||
|  | 	ihm inbound.Manager | ||||||
|  | 	ohm outbound.Manager | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *handlerServer) AddInbound(ctx context.Context, request *AddInboundRequest) (*AddInboundResponse, error) { | ||||||
|  | 	if err := core.AddInboundHandler(s.s, request.Inbound); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return &AddInboundResponse{}, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *handlerServer) RemoveInbound(ctx context.Context, request *RemoveInboundRequest) (*RemoveInboundResponse, error) { | ||||||
|  | 	return &RemoveInboundResponse{}, s.ihm.RemoveHandler(ctx, request.Tag) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *handlerServer) AlterInbound(ctx context.Context, request *AlterInboundRequest) (*AlterInboundResponse, error) { | ||||||
|  | 	rawOperation, err := request.Operation.GetInstance() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, newError("unknown operation").Base(err) | ||||||
|  | 	} | ||||||
|  | 	operation, ok := rawOperation.(InboundOperation) | ||||||
|  | 	if !ok { | ||||||
|  | 		return nil, newError("not an inbound operation") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	handler, err := s.ihm.GetHandler(ctx, request.Tag) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, newError("failed to get handler: ", request.Tag).Base(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return &AlterInboundResponse{}, operation.ApplyInbound(ctx, handler) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *handlerServer) AddOutbound(ctx context.Context, request *AddOutboundRequest) (*AddOutboundResponse, error) { | ||||||
|  | 	if err := core.AddOutboundHandler(s.s, request.Outbound); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return &AddOutboundResponse{}, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *handlerServer) RemoveOutbound(ctx context.Context, request *RemoveOutboundRequest) (*RemoveOutboundResponse, error) { | ||||||
|  | 	return &RemoveOutboundResponse{}, s.ohm.RemoveHandler(ctx, request.Tag) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *handlerServer) AlterOutbound(ctx context.Context, request *AlterOutboundRequest) (*AlterOutboundResponse, error) { | ||||||
|  | 	rawOperation, err := request.Operation.GetInstance() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, newError("unknown operation").Base(err) | ||||||
|  | 	} | ||||||
|  | 	operation, ok := rawOperation.(OutboundOperation) | ||||||
|  | 	if !ok { | ||||||
|  | 		return nil, newError("not an outbound operation") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	handler := s.ohm.GetHandler(request.Tag) | ||||||
|  | 	return &AlterOutboundResponse{}, operation.ApplyOutbound(ctx, handler) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *handlerServer) mustEmbedUnimplementedHandlerServiceServer() {} | ||||||
|  |  | ||||||
|  | type service struct { | ||||||
|  | 	v *core.Instance | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *service) Register(server *grpc.Server) { | ||||||
|  | 	hs := &handlerServer{ | ||||||
|  | 		s: s.v, | ||||||
|  | 	} | ||||||
|  | 	common.Must(s.v.RequireFeatures(func(im inbound.Manager, om outbound.Manager) { | ||||||
|  | 		hs.ihm = im | ||||||
|  | 		hs.ohm = om | ||||||
|  | 	})) | ||||||
|  | 	RegisterHandlerServiceServer(server, hs) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func init() { | ||||||
|  | 	common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, cfg interface{}) (interface{}, error) { | ||||||
|  | 		s := core.MustFromContext(ctx) | ||||||
|  | 		return &service{v: s}, nil | ||||||
|  | 	})) | ||||||
|  | } | ||||||
							
								
								
									
										1065
									
								
								app/proxyman/command/command.pb.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1065
									
								
								app/proxyman/command/command.pb.go
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										73
									
								
								app/proxyman/command/command.proto
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								app/proxyman/command/command.proto
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,73 @@ | |||||||
|  | syntax = "proto3"; | ||||||
|  |  | ||||||
|  | package xray.app.proxyman.command; | ||||||
|  | option csharp_namespace = "Xray.App.Proxyman.Command"; | ||||||
|  | option go_package = "github.com/xtls/xray-core/v1/app/proxyman/command"; | ||||||
|  | option java_package = "com.xray.app.proxyman.command"; | ||||||
|  | option java_multiple_files = true; | ||||||
|  |  | ||||||
|  | import "common/protocol/user.proto"; | ||||||
|  | import "common/serial/typed_message.proto"; | ||||||
|  | import "core/config.proto"; | ||||||
|  |  | ||||||
|  | message AddUserOperation { | ||||||
|  |   xray.common.protocol.User user = 1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message RemoveUserOperation { | ||||||
|  |   string email = 1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message AddInboundRequest { | ||||||
|  |   core.InboundHandlerConfig inbound = 1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message AddInboundResponse {} | ||||||
|  |  | ||||||
|  | message RemoveInboundRequest { | ||||||
|  |   string tag = 1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message RemoveInboundResponse {} | ||||||
|  |  | ||||||
|  | message AlterInboundRequest { | ||||||
|  |   string tag = 1; | ||||||
|  |   xray.common.serial.TypedMessage operation = 2; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message AlterInboundResponse {} | ||||||
|  |  | ||||||
|  | message AddOutboundRequest { | ||||||
|  |   core.OutboundHandlerConfig outbound = 1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message AddOutboundResponse {} | ||||||
|  |  | ||||||
|  | message RemoveOutboundRequest { | ||||||
|  |   string tag = 1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message RemoveOutboundResponse {} | ||||||
|  |  | ||||||
|  | message AlterOutboundRequest { | ||||||
|  |   string tag = 1; | ||||||
|  |   xray.common.serial.TypedMessage operation = 2; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message AlterOutboundResponse {} | ||||||
|  |  | ||||||
|  | service HandlerService { | ||||||
|  |   rpc AddInbound(AddInboundRequest) returns (AddInboundResponse) {} | ||||||
|  |  | ||||||
|  |   rpc RemoveInbound(RemoveInboundRequest) returns (RemoveInboundResponse) {} | ||||||
|  |  | ||||||
|  |   rpc AlterInbound(AlterInboundRequest) returns (AlterInboundResponse) {} | ||||||
|  |  | ||||||
|  |   rpc AddOutbound(AddOutboundRequest) returns (AddOutboundResponse) {} | ||||||
|  |  | ||||||
|  |   rpc RemoveOutbound(RemoveOutboundRequest) returns (RemoveOutboundResponse) {} | ||||||
|  |  | ||||||
|  |   rpc AlterOutbound(AlterOutboundRequest) returns (AlterOutboundResponse) {} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message Config {} | ||||||
							
								
								
									
										277
									
								
								app/proxyman/command/command_grpc.pb.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										277
									
								
								app/proxyman/command/command_grpc.pb.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,277 @@ | |||||||
|  | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. | ||||||
|  |  | ||||||
|  | package command | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	context "context" | ||||||
|  | 	grpc "google.golang.org/grpc" | ||||||
|  | 	codes "google.golang.org/grpc/codes" | ||||||
|  | 	status "google.golang.org/grpc/status" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // This is a compile-time assertion to ensure that this generated file | ||||||
|  | // is compatible with the grpc package it is being compiled against. | ||||||
|  | const _ = grpc.SupportPackageIsVersion7 | ||||||
|  |  | ||||||
|  | // HandlerServiceClient is the client API for HandlerService service. | ||||||
|  | // | ||||||
|  | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. | ||||||
|  | type HandlerServiceClient interface { | ||||||
|  | 	AddInbound(ctx context.Context, in *AddInboundRequest, opts ...grpc.CallOption) (*AddInboundResponse, error) | ||||||
|  | 	RemoveInbound(ctx context.Context, in *RemoveInboundRequest, opts ...grpc.CallOption) (*RemoveInboundResponse, error) | ||||||
|  | 	AlterInbound(ctx context.Context, in *AlterInboundRequest, opts ...grpc.CallOption) (*AlterInboundResponse, error) | ||||||
|  | 	AddOutbound(ctx context.Context, in *AddOutboundRequest, opts ...grpc.CallOption) (*AddOutboundResponse, error) | ||||||
|  | 	RemoveOutbound(ctx context.Context, in *RemoveOutboundRequest, opts ...grpc.CallOption) (*RemoveOutboundResponse, error) | ||||||
|  | 	AlterOutbound(ctx context.Context, in *AlterOutboundRequest, opts ...grpc.CallOption) (*AlterOutboundResponse, error) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type handlerServiceClient struct { | ||||||
|  | 	cc grpc.ClientConnInterface | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewHandlerServiceClient(cc grpc.ClientConnInterface) HandlerServiceClient { | ||||||
|  | 	return &handlerServiceClient{cc} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *handlerServiceClient) AddInbound(ctx context.Context, in *AddInboundRequest, opts ...grpc.CallOption) (*AddInboundResponse, error) { | ||||||
|  | 	out := new(AddInboundResponse) | ||||||
|  | 	err := c.cc.Invoke(ctx, "/xray.app.proxyman.command.HandlerService/AddInbound", in, out, opts...) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return out, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *handlerServiceClient) RemoveInbound(ctx context.Context, in *RemoveInboundRequest, opts ...grpc.CallOption) (*RemoveInboundResponse, error) { | ||||||
|  | 	out := new(RemoveInboundResponse) | ||||||
|  | 	err := c.cc.Invoke(ctx, "/xray.app.proxyman.command.HandlerService/RemoveInbound", in, out, opts...) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return out, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *handlerServiceClient) AlterInbound(ctx context.Context, in *AlterInboundRequest, opts ...grpc.CallOption) (*AlterInboundResponse, error) { | ||||||
|  | 	out := new(AlterInboundResponse) | ||||||
|  | 	err := c.cc.Invoke(ctx, "/xray.app.proxyman.command.HandlerService/AlterInbound", in, out, opts...) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return out, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *handlerServiceClient) AddOutbound(ctx context.Context, in *AddOutboundRequest, opts ...grpc.CallOption) (*AddOutboundResponse, error) { | ||||||
|  | 	out := new(AddOutboundResponse) | ||||||
|  | 	err := c.cc.Invoke(ctx, "/xray.app.proxyman.command.HandlerService/AddOutbound", in, out, opts...) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return out, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *handlerServiceClient) RemoveOutbound(ctx context.Context, in *RemoveOutboundRequest, opts ...grpc.CallOption) (*RemoveOutboundResponse, error) { | ||||||
|  | 	out := new(RemoveOutboundResponse) | ||||||
|  | 	err := c.cc.Invoke(ctx, "/xray.app.proxyman.command.HandlerService/RemoveOutbound", in, out, opts...) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return out, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *handlerServiceClient) AlterOutbound(ctx context.Context, in *AlterOutboundRequest, opts ...grpc.CallOption) (*AlterOutboundResponse, error) { | ||||||
|  | 	out := new(AlterOutboundResponse) | ||||||
|  | 	err := c.cc.Invoke(ctx, "/xray.app.proxyman.command.HandlerService/AlterOutbound", in, out, opts...) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return out, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // HandlerServiceServer is the server API for HandlerService service. | ||||||
|  | // All implementations must embed UnimplementedHandlerServiceServer | ||||||
|  | // for forward compatibility | ||||||
|  | type HandlerServiceServer interface { | ||||||
|  | 	AddInbound(context.Context, *AddInboundRequest) (*AddInboundResponse, error) | ||||||
|  | 	RemoveInbound(context.Context, *RemoveInboundRequest) (*RemoveInboundResponse, error) | ||||||
|  | 	AlterInbound(context.Context, *AlterInboundRequest) (*AlterInboundResponse, error) | ||||||
|  | 	AddOutbound(context.Context, *AddOutboundRequest) (*AddOutboundResponse, error) | ||||||
|  | 	RemoveOutbound(context.Context, *RemoveOutboundRequest) (*RemoveOutboundResponse, error) | ||||||
|  | 	AlterOutbound(context.Context, *AlterOutboundRequest) (*AlterOutboundResponse, error) | ||||||
|  | 	mustEmbedUnimplementedHandlerServiceServer() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // UnimplementedHandlerServiceServer must be embedded to have forward compatible implementations. | ||||||
|  | type UnimplementedHandlerServiceServer struct { | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (UnimplementedHandlerServiceServer) AddInbound(context.Context, *AddInboundRequest) (*AddInboundResponse, error) { | ||||||
|  | 	return nil, status.Errorf(codes.Unimplemented, "method AddInbound not implemented") | ||||||
|  | } | ||||||
|  | func (UnimplementedHandlerServiceServer) RemoveInbound(context.Context, *RemoveInboundRequest) (*RemoveInboundResponse, error) { | ||||||
|  | 	return nil, status.Errorf(codes.Unimplemented, "method RemoveInbound not implemented") | ||||||
|  | } | ||||||
|  | func (UnimplementedHandlerServiceServer) AlterInbound(context.Context, *AlterInboundRequest) (*AlterInboundResponse, error) { | ||||||
|  | 	return nil, status.Errorf(codes.Unimplemented, "method AlterInbound not implemented") | ||||||
|  | } | ||||||
|  | func (UnimplementedHandlerServiceServer) AddOutbound(context.Context, *AddOutboundRequest) (*AddOutboundResponse, error) { | ||||||
|  | 	return nil, status.Errorf(codes.Unimplemented, "method AddOutbound not implemented") | ||||||
|  | } | ||||||
|  | func (UnimplementedHandlerServiceServer) RemoveOutbound(context.Context, *RemoveOutboundRequest) (*RemoveOutboundResponse, error) { | ||||||
|  | 	return nil, status.Errorf(codes.Unimplemented, "method RemoveOutbound not implemented") | ||||||
|  | } | ||||||
|  | func (UnimplementedHandlerServiceServer) AlterOutbound(context.Context, *AlterOutboundRequest) (*AlterOutboundResponse, error) { | ||||||
|  | 	return nil, status.Errorf(codes.Unimplemented, "method AlterOutbound not implemented") | ||||||
|  | } | ||||||
|  | func (UnimplementedHandlerServiceServer) mustEmbedUnimplementedHandlerServiceServer() {} | ||||||
|  |  | ||||||
|  | // UnsafeHandlerServiceServer may be embedded to opt out of forward compatibility for this service. | ||||||
|  | // Use of this interface is not recommended, as added methods to HandlerServiceServer will | ||||||
|  | // result in compilation errors. | ||||||
|  | type UnsafeHandlerServiceServer interface { | ||||||
|  | 	mustEmbedUnimplementedHandlerServiceServer() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func RegisterHandlerServiceServer(s grpc.ServiceRegistrar, srv HandlerServiceServer) { | ||||||
|  | 	s.RegisterService(&_HandlerService_serviceDesc, srv) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func _HandlerService_AddInbound_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { | ||||||
|  | 	in := new(AddInboundRequest) | ||||||
|  | 	if err := dec(in); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	if interceptor == nil { | ||||||
|  | 		return srv.(HandlerServiceServer).AddInbound(ctx, in) | ||||||
|  | 	} | ||||||
|  | 	info := &grpc.UnaryServerInfo{ | ||||||
|  | 		Server:     srv, | ||||||
|  | 		FullMethod: "/xray.app.proxyman.command.HandlerService/AddInbound", | ||||||
|  | 	} | ||||||
|  | 	handler := func(ctx context.Context, req interface{}) (interface{}, error) { | ||||||
|  | 		return srv.(HandlerServiceServer).AddInbound(ctx, req.(*AddInboundRequest)) | ||||||
|  | 	} | ||||||
|  | 	return interceptor(ctx, in, info, handler) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func _HandlerService_RemoveInbound_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { | ||||||
|  | 	in := new(RemoveInboundRequest) | ||||||
|  | 	if err := dec(in); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	if interceptor == nil { | ||||||
|  | 		return srv.(HandlerServiceServer).RemoveInbound(ctx, in) | ||||||
|  | 	} | ||||||
|  | 	info := &grpc.UnaryServerInfo{ | ||||||
|  | 		Server:     srv, | ||||||
|  | 		FullMethod: "/xray.app.proxyman.command.HandlerService/RemoveInbound", | ||||||
|  | 	} | ||||||
|  | 	handler := func(ctx context.Context, req interface{}) (interface{}, error) { | ||||||
|  | 		return srv.(HandlerServiceServer).RemoveInbound(ctx, req.(*RemoveInboundRequest)) | ||||||
|  | 	} | ||||||
|  | 	return interceptor(ctx, in, info, handler) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func _HandlerService_AlterInbound_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { | ||||||
|  | 	in := new(AlterInboundRequest) | ||||||
|  | 	if err := dec(in); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	if interceptor == nil { | ||||||
|  | 		return srv.(HandlerServiceServer).AlterInbound(ctx, in) | ||||||
|  | 	} | ||||||
|  | 	info := &grpc.UnaryServerInfo{ | ||||||
|  | 		Server:     srv, | ||||||
|  | 		FullMethod: "/xray.app.proxyman.command.HandlerService/AlterInbound", | ||||||
|  | 	} | ||||||
|  | 	handler := func(ctx context.Context, req interface{}) (interface{}, error) { | ||||||
|  | 		return srv.(HandlerServiceServer).AlterInbound(ctx, req.(*AlterInboundRequest)) | ||||||
|  | 	} | ||||||
|  | 	return interceptor(ctx, in, info, handler) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func _HandlerService_AddOutbound_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { | ||||||
|  | 	in := new(AddOutboundRequest) | ||||||
|  | 	if err := dec(in); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	if interceptor == nil { | ||||||
|  | 		return srv.(HandlerServiceServer).AddOutbound(ctx, in) | ||||||
|  | 	} | ||||||
|  | 	info := &grpc.UnaryServerInfo{ | ||||||
|  | 		Server:     srv, | ||||||
|  | 		FullMethod: "/xray.app.proxyman.command.HandlerService/AddOutbound", | ||||||
|  | 	} | ||||||
|  | 	handler := func(ctx context.Context, req interface{}) (interface{}, error) { | ||||||
|  | 		return srv.(HandlerServiceServer).AddOutbound(ctx, req.(*AddOutboundRequest)) | ||||||
|  | 	} | ||||||
|  | 	return interceptor(ctx, in, info, handler) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func _HandlerService_RemoveOutbound_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { | ||||||
|  | 	in := new(RemoveOutboundRequest) | ||||||
|  | 	if err := dec(in); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	if interceptor == nil { | ||||||
|  | 		return srv.(HandlerServiceServer).RemoveOutbound(ctx, in) | ||||||
|  | 	} | ||||||
|  | 	info := &grpc.UnaryServerInfo{ | ||||||
|  | 		Server:     srv, | ||||||
|  | 		FullMethod: "/xray.app.proxyman.command.HandlerService/RemoveOutbound", | ||||||
|  | 	} | ||||||
|  | 	handler := func(ctx context.Context, req interface{}) (interface{}, error) { | ||||||
|  | 		return srv.(HandlerServiceServer).RemoveOutbound(ctx, req.(*RemoveOutboundRequest)) | ||||||
|  | 	} | ||||||
|  | 	return interceptor(ctx, in, info, handler) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func _HandlerService_AlterOutbound_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { | ||||||
|  | 	in := new(AlterOutboundRequest) | ||||||
|  | 	if err := dec(in); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	if interceptor == nil { | ||||||
|  | 		return srv.(HandlerServiceServer).AlterOutbound(ctx, in) | ||||||
|  | 	} | ||||||
|  | 	info := &grpc.UnaryServerInfo{ | ||||||
|  | 		Server:     srv, | ||||||
|  | 		FullMethod: "/xray.app.proxyman.command.HandlerService/AlterOutbound", | ||||||
|  | 	} | ||||||
|  | 	handler := func(ctx context.Context, req interface{}) (interface{}, error) { | ||||||
|  | 		return srv.(HandlerServiceServer).AlterOutbound(ctx, req.(*AlterOutboundRequest)) | ||||||
|  | 	} | ||||||
|  | 	return interceptor(ctx, in, info, handler) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var _HandlerService_serviceDesc = grpc.ServiceDesc{ | ||||||
|  | 	ServiceName: "xray.app.proxyman.command.HandlerService", | ||||||
|  | 	HandlerType: (*HandlerServiceServer)(nil), | ||||||
|  | 	Methods: []grpc.MethodDesc{ | ||||||
|  | 		{ | ||||||
|  | 			MethodName: "AddInbound", | ||||||
|  | 			Handler:    _HandlerService_AddInbound_Handler, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			MethodName: "RemoveInbound", | ||||||
|  | 			Handler:    _HandlerService_RemoveInbound_Handler, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			MethodName: "AlterInbound", | ||||||
|  | 			Handler:    _HandlerService_AlterInbound_Handler, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			MethodName: "AddOutbound", | ||||||
|  | 			Handler:    _HandlerService_AddOutbound_Handler, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			MethodName: "RemoveOutbound", | ||||||
|  | 			Handler:    _HandlerService_RemoveOutbound_Handler, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			MethodName: "AlterOutbound", | ||||||
|  | 			Handler:    _HandlerService_AlterOutbound_Handler, | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | 	Streams:  []grpc.StreamDesc{}, | ||||||
|  | 	Metadata: "app/proxyman/command/command.proto", | ||||||
|  | } | ||||||
							
								
								
									
										3
									
								
								app/proxyman/command/doc.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								app/proxyman/command/doc.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | package command | ||||||
|  |  | ||||||
|  | //go:generate go run github.com/xtls/xray-core/v1/common/errors/errorgen | ||||||
							
								
								
									
										9
									
								
								app/proxyman/command/errors.generated.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								app/proxyman/command/errors.generated.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | package command | ||||||
|  |  | ||||||
|  | import "github.com/xtls/xray-core/v1/common/errors" | ||||||
|  |  | ||||||
|  | type errPathObjHolder struct{} | ||||||
|  |  | ||||||
|  | func newError(values ...interface{}) *errors.Error { | ||||||
|  | 	return errors.New(values...).WithPathObj(errPathObjHolder{}) | ||||||
|  | } | ||||||
							
								
								
									
										39
									
								
								app/proxyman/config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								app/proxyman/config.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | |||||||
|  | package proxyman | ||||||
|  |  | ||||||
|  | func (s *AllocationStrategy) GetConcurrencyValue() uint32 { | ||||||
|  | 	if s == nil || s.Concurrency == nil { | ||||||
|  | 		return 3 | ||||||
|  | 	} | ||||||
|  | 	return s.Concurrency.Value | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *AllocationStrategy) GetRefreshValue() uint32 { | ||||||
|  | 	if s == nil || s.Refresh == nil { | ||||||
|  | 		return 5 | ||||||
|  | 	} | ||||||
|  | 	return s.Refresh.Value | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *ReceiverConfig) GetEffectiveSniffingSettings() *SniffingConfig { | ||||||
|  | 	if c.SniffingSettings != nil { | ||||||
|  | 		return c.SniffingSettings | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(c.DomainOverride) > 0 { | ||||||
|  | 		var p []string | ||||||
|  | 		for _, kd := range c.DomainOverride { | ||||||
|  | 			switch kd { | ||||||
|  | 			case KnownProtocols_HTTP: | ||||||
|  | 				p = append(p, "http") | ||||||
|  | 			case KnownProtocols_TLS: | ||||||
|  | 				p = append(p, "tls") | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		return &SniffingConfig{ | ||||||
|  | 			Enabled:             true, | ||||||
|  | 			DestinationOverride: p, | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
							
								
								
									
										1049
									
								
								app/proxyman/config.pb.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1049
									
								
								app/proxyman/config.pb.go
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										97
									
								
								app/proxyman/config.proto
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								app/proxyman/config.proto
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,97 @@ | |||||||
|  | syntax = "proto3"; | ||||||
|  |  | ||||||
|  | package xray.app.proxyman; | ||||||
|  | option csharp_namespace = "Xray.App.Proxyman"; | ||||||
|  | option go_package = "github.com/xtls/xray-core/v1/app/proxyman"; | ||||||
|  | option java_package = "com.xray.app.proxyman"; | ||||||
|  | option java_multiple_files = true; | ||||||
|  |  | ||||||
|  | import "common/net/address.proto"; | ||||||
|  | import "common/net/port.proto"; | ||||||
|  | import "transport/internet/config.proto"; | ||||||
|  | import "common/serial/typed_message.proto"; | ||||||
|  |  | ||||||
|  | message InboundConfig {} | ||||||
|  |  | ||||||
|  | message AllocationStrategy { | ||||||
|  |   enum Type { | ||||||
|  |     // Always allocate all connection handlers. | ||||||
|  |     Always = 0; | ||||||
|  |  | ||||||
|  |     // Randomly allocate specific range of handlers. | ||||||
|  |     Random = 1; | ||||||
|  |  | ||||||
|  |     // External. Not supported yet. | ||||||
|  |     External = 2; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Type type = 1; | ||||||
|  |  | ||||||
|  |   message AllocationStrategyConcurrency { | ||||||
|  |     uint32 value = 1; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Number of handlers (ports) running in parallel. | ||||||
|  |   // Default value is 3 if unset. | ||||||
|  |   AllocationStrategyConcurrency concurrency = 2; | ||||||
|  |  | ||||||
|  |   message AllocationStrategyRefresh { | ||||||
|  |     uint32 value = 1; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Number of minutes before a handler is regenerated. | ||||||
|  |   // Default value is 5 if unset. | ||||||
|  |   AllocationStrategyRefresh refresh = 3; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | enum KnownProtocols { | ||||||
|  |   HTTP = 0; | ||||||
|  |   TLS = 1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message SniffingConfig { | ||||||
|  |   // Whether or not to enable content sniffing on an inbound connection. | ||||||
|  |   bool enabled = 1; | ||||||
|  |  | ||||||
|  |   // Override target destination if sniff'ed protocol is in the given list. | ||||||
|  |   // Supported values are "http", "tls". | ||||||
|  |   repeated string destination_override = 2; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message ReceiverConfig { | ||||||
|  |   // PortRange specifies the ports which the Receiver should listen on. | ||||||
|  |   xray.common.net.PortRange port_range = 1; | ||||||
|  |   // Listen specifies the IP address that the Receiver should listen on. | ||||||
|  |   xray.common.net.IPOrDomain listen = 2; | ||||||
|  |   AllocationStrategy allocation_strategy = 3; | ||||||
|  |   xray.transport.internet.StreamConfig stream_settings = 4; | ||||||
|  |   bool receive_original_destination = 5; | ||||||
|  |   reserved 6; | ||||||
|  |   // Override domains for the given protocol. | ||||||
|  |   // Deprecated. Use sniffing_settings. | ||||||
|  |   repeated KnownProtocols domain_override = 7 [deprecated = true]; | ||||||
|  |   SniffingConfig sniffing_settings = 8; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message InboundHandlerConfig { | ||||||
|  |   string tag = 1; | ||||||
|  |   xray.common.serial.TypedMessage receiver_settings = 2; | ||||||
|  |   xray.common.serial.TypedMessage proxy_settings = 3; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message OutboundConfig {} | ||||||
|  |  | ||||||
|  | message SenderConfig { | ||||||
|  |   // Send traffic through the given IP. Only IP is allowed. | ||||||
|  |   xray.common.net.IPOrDomain via = 1; | ||||||
|  |   xray.transport.internet.StreamConfig stream_settings = 2; | ||||||
|  |   xray.transport.internet.ProxyConfig proxy_settings = 3; | ||||||
|  |   MultiplexingConfig multiplex_settings = 4; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message MultiplexingConfig { | ||||||
|  |   // Whether or not Mux is enabled. | ||||||
|  |   bool enabled = 1; | ||||||
|  |   // Max number of concurrent connections that one Mux connection can handle. | ||||||
|  |   uint32 concurrency = 2; | ||||||
|  | } | ||||||
							
								
								
									
										185
									
								
								app/proxyman/inbound/always.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										185
									
								
								app/proxyman/inbound/always.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,185 @@ | |||||||
|  | package inbound | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  |  | ||||||
|  | 	"github.com/xtls/xray-core/v1/app/proxyman" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/dice" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/errors" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/mux" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/net" | ||||||
|  | 	"github.com/xtls/xray-core/v1/core" | ||||||
|  | 	"github.com/xtls/xray-core/v1/features/policy" | ||||||
|  | 	"github.com/xtls/xray-core/v1/features/stats" | ||||||
|  | 	"github.com/xtls/xray-core/v1/proxy" | ||||||
|  | 	"github.com/xtls/xray-core/v1/transport/internet" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func getStatCounter(v *core.Instance, tag string) (stats.Counter, stats.Counter) { | ||||||
|  | 	var uplinkCounter stats.Counter | ||||||
|  | 	var downlinkCounter stats.Counter | ||||||
|  |  | ||||||
|  | 	policy := v.GetFeature(policy.ManagerType()).(policy.Manager) | ||||||
|  | 	if len(tag) > 0 && policy.ForSystem().Stats.InboundUplink { | ||||||
|  | 		statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager) | ||||||
|  | 		name := "inbound>>>" + tag + ">>>traffic>>>uplink" | ||||||
|  | 		c, _ := stats.GetOrRegisterCounter(statsManager, name) | ||||||
|  | 		if c != nil { | ||||||
|  | 			uplinkCounter = c | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if len(tag) > 0 && policy.ForSystem().Stats.InboundDownlink { | ||||||
|  | 		statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager) | ||||||
|  | 		name := "inbound>>>" + tag + ">>>traffic>>>downlink" | ||||||
|  | 		c, _ := stats.GetOrRegisterCounter(statsManager, name) | ||||||
|  | 		if c != nil { | ||||||
|  | 			downlinkCounter = c | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return uplinkCounter, downlinkCounter | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type AlwaysOnInboundHandler struct { | ||||||
|  | 	proxy   proxy.Inbound | ||||||
|  | 	workers []worker | ||||||
|  | 	mux     *mux.Server | ||||||
|  | 	tag     string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *proxyman.ReceiverConfig, proxyConfig interface{}) (*AlwaysOnInboundHandler, error) { | ||||||
|  | 	rawProxy, err := common.CreateObject(ctx, proxyConfig) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	p, ok := rawProxy.(proxy.Inbound) | ||||||
|  | 	if !ok { | ||||||
|  | 		return nil, newError("not an inbound proxy.") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	h := &AlwaysOnInboundHandler{ | ||||||
|  | 		proxy: p, | ||||||
|  | 		mux:   mux.NewServer(ctx), | ||||||
|  | 		tag:   tag, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	uplinkCounter, downlinkCounter := getStatCounter(core.MustFromContext(ctx), tag) | ||||||
|  |  | ||||||
|  | 	nl := p.Network() | ||||||
|  | 	pr := receiverConfig.PortRange | ||||||
|  | 	address := receiverConfig.Listen.AsAddress() | ||||||
|  | 	if address == nil { | ||||||
|  | 		address = net.AnyIP | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	mss, err := internet.ToMemoryStreamConfig(receiverConfig.StreamSettings) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, newError("failed to parse stream config").Base(err).AtWarning() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if receiverConfig.ReceiveOriginalDestination { | ||||||
|  | 		if mss.SocketSettings == nil { | ||||||
|  | 			mss.SocketSettings = &internet.SocketConfig{} | ||||||
|  | 		} | ||||||
|  | 		if mss.SocketSettings.Tproxy == internet.SocketConfig_Off { | ||||||
|  | 			mss.SocketSettings.Tproxy = internet.SocketConfig_Redirect | ||||||
|  | 		} | ||||||
|  | 		mss.SocketSettings.ReceiveOriginalDestAddress = true | ||||||
|  | 	} | ||||||
|  | 	if pr == nil { | ||||||
|  | 		if net.HasNetwork(nl, net.Network_UNIX) { | ||||||
|  | 			newError("creating unix domain socket worker on ", address).AtDebug().WriteToLog() | ||||||
|  |  | ||||||
|  | 			worker := &dsWorker{ | ||||||
|  | 				address:         address, | ||||||
|  | 				proxy:           p, | ||||||
|  | 				stream:          mss, | ||||||
|  | 				tag:             tag, | ||||||
|  | 				dispatcher:      h.mux, | ||||||
|  | 				sniffingConfig:  receiverConfig.GetEffectiveSniffingSettings(), | ||||||
|  | 				uplinkCounter:   uplinkCounter, | ||||||
|  | 				downlinkCounter: downlinkCounter, | ||||||
|  | 				ctx:             ctx, | ||||||
|  | 			} | ||||||
|  | 			h.workers = append(h.workers, worker) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if pr != nil { | ||||||
|  | 		for port := pr.From; port <= pr.To; port++ { | ||||||
|  | 			if net.HasNetwork(nl, net.Network_TCP) { | ||||||
|  | 				newError("creating stream worker on ", address, ":", port).AtDebug().WriteToLog() | ||||||
|  |  | ||||||
|  | 				worker := &tcpWorker{ | ||||||
|  | 					address:         address, | ||||||
|  | 					port:            net.Port(port), | ||||||
|  | 					proxy:           p, | ||||||
|  | 					stream:          mss, | ||||||
|  | 					recvOrigDest:    receiverConfig.ReceiveOriginalDestination, | ||||||
|  | 					tag:             tag, | ||||||
|  | 					dispatcher:      h.mux, | ||||||
|  | 					sniffingConfig:  receiverConfig.GetEffectiveSniffingSettings(), | ||||||
|  | 					uplinkCounter:   uplinkCounter, | ||||||
|  | 					downlinkCounter: downlinkCounter, | ||||||
|  | 					ctx:             ctx, | ||||||
|  | 				} | ||||||
|  | 				h.workers = append(h.workers, worker) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if net.HasNetwork(nl, net.Network_UDP) { | ||||||
|  | 				worker := &udpWorker{ | ||||||
|  | 					tag:             tag, | ||||||
|  | 					proxy:           p, | ||||||
|  | 					address:         address, | ||||||
|  | 					port:            net.Port(port), | ||||||
|  | 					dispatcher:      h.mux, | ||||||
|  | 					uplinkCounter:   uplinkCounter, | ||||||
|  | 					downlinkCounter: downlinkCounter, | ||||||
|  | 					stream:          mss, | ||||||
|  | 				} | ||||||
|  | 				h.workers = append(h.workers, worker) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return h, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Start implements common.Runnable. | ||||||
|  | func (h *AlwaysOnInboundHandler) Start() error { | ||||||
|  | 	for _, worker := range h.workers { | ||||||
|  | 		if err := worker.Start(); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Close implements common.Closable. | ||||||
|  | func (h *AlwaysOnInboundHandler) Close() error { | ||||||
|  | 	var errs []error | ||||||
|  | 	for _, worker := range h.workers { | ||||||
|  | 		errs = append(errs, worker.Close()) | ||||||
|  | 	} | ||||||
|  | 	errs = append(errs, h.mux.Close()) | ||||||
|  | 	if err := errors.Combine(errs...); err != nil { | ||||||
|  | 		return newError("failed to close all resources").Base(err) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (h *AlwaysOnInboundHandler) GetRandomInboundProxy() (interface{}, net.Port, int) { | ||||||
|  | 	if len(h.workers) == 0 { | ||||||
|  | 		return nil, 0, 0 | ||||||
|  | 	} | ||||||
|  | 	w := h.workers[dice.Roll(len(h.workers))] | ||||||
|  | 	return w.Proxy(), w.Port(), 9999 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (h *AlwaysOnInboundHandler) Tag() string { | ||||||
|  | 	return h.tag | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (h *AlwaysOnInboundHandler) GetInbound() proxy.Inbound { | ||||||
|  | 	return h.proxy | ||||||
|  | } | ||||||
							
								
								
									
										201
									
								
								app/proxyman/inbound/dynamic.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										201
									
								
								app/proxyman/inbound/dynamic.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,201 @@ | |||||||
|  | package inbound | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"sync" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/xtls/xray-core/v1/app/proxyman" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/dice" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/mux" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/net" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/task" | ||||||
|  | 	"github.com/xtls/xray-core/v1/core" | ||||||
|  | 	"github.com/xtls/xray-core/v1/proxy" | ||||||
|  | 	"github.com/xtls/xray-core/v1/transport/internet" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type DynamicInboundHandler struct { | ||||||
|  | 	tag            string | ||||||
|  | 	v              *core.Instance | ||||||
|  | 	proxyConfig    interface{} | ||||||
|  | 	receiverConfig *proxyman.ReceiverConfig | ||||||
|  | 	streamSettings *internet.MemoryStreamConfig | ||||||
|  | 	portMutex      sync.Mutex | ||||||
|  | 	portsInUse     map[net.Port]bool | ||||||
|  | 	workerMutex    sync.RWMutex | ||||||
|  | 	worker         []worker | ||||||
|  | 	lastRefresh    time.Time | ||||||
|  | 	mux            *mux.Server | ||||||
|  | 	task           *task.Periodic | ||||||
|  |  | ||||||
|  | 	ctx context.Context | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewDynamicInboundHandler(ctx context.Context, tag string, receiverConfig *proxyman.ReceiverConfig, proxyConfig interface{}) (*DynamicInboundHandler, error) { | ||||||
|  | 	v := core.MustFromContext(ctx) | ||||||
|  | 	h := &DynamicInboundHandler{ | ||||||
|  | 		tag:            tag, | ||||||
|  | 		proxyConfig:    proxyConfig, | ||||||
|  | 		receiverConfig: receiverConfig, | ||||||
|  | 		portsInUse:     make(map[net.Port]bool), | ||||||
|  | 		mux:            mux.NewServer(ctx), | ||||||
|  | 		v:              v, | ||||||
|  | 		ctx:            ctx, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	mss, err := internet.ToMemoryStreamConfig(receiverConfig.StreamSettings) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, newError("failed to parse stream settings").Base(err).AtWarning() | ||||||
|  | 	} | ||||||
|  | 	if receiverConfig.ReceiveOriginalDestination { | ||||||
|  | 		if mss.SocketSettings == nil { | ||||||
|  | 			mss.SocketSettings = &internet.SocketConfig{} | ||||||
|  | 		} | ||||||
|  | 		if mss.SocketSettings.Tproxy == internet.SocketConfig_Off { | ||||||
|  | 			mss.SocketSettings.Tproxy = internet.SocketConfig_Redirect | ||||||
|  | 		} | ||||||
|  | 		mss.SocketSettings.ReceiveOriginalDestAddress = true | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	h.streamSettings = mss | ||||||
|  |  | ||||||
|  | 	h.task = &task.Periodic{ | ||||||
|  | 		Interval: time.Minute * time.Duration(h.receiverConfig.AllocationStrategy.GetRefreshValue()), | ||||||
|  | 		Execute:  h.refresh, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return h, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (h *DynamicInboundHandler) allocatePort() net.Port { | ||||||
|  | 	from := int(h.receiverConfig.PortRange.From) | ||||||
|  | 	delta := int(h.receiverConfig.PortRange.To) - from + 1 | ||||||
|  |  | ||||||
|  | 	h.portMutex.Lock() | ||||||
|  | 	defer h.portMutex.Unlock() | ||||||
|  |  | ||||||
|  | 	for { | ||||||
|  | 		r := dice.Roll(delta) | ||||||
|  | 		port := net.Port(from + r) | ||||||
|  | 		_, used := h.portsInUse[port] | ||||||
|  | 		if !used { | ||||||
|  | 			h.portsInUse[port] = true | ||||||
|  | 			return port | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (h *DynamicInboundHandler) closeWorkers(workers []worker) { | ||||||
|  | 	ports2Del := make([]net.Port, len(workers)) | ||||||
|  | 	for idx, worker := range workers { | ||||||
|  | 		ports2Del[idx] = worker.Port() | ||||||
|  | 		if err := worker.Close(); err != nil { | ||||||
|  | 			newError("failed to close worker").Base(err).WriteToLog() | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	h.portMutex.Lock() | ||||||
|  | 	for _, port := range ports2Del { | ||||||
|  | 		delete(h.portsInUse, port) | ||||||
|  | 	} | ||||||
|  | 	h.portMutex.Unlock() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (h *DynamicInboundHandler) refresh() error { | ||||||
|  | 	h.lastRefresh = time.Now() | ||||||
|  |  | ||||||
|  | 	timeout := time.Minute * time.Duration(h.receiverConfig.AllocationStrategy.GetRefreshValue()) * 2 | ||||||
|  | 	concurrency := h.receiverConfig.AllocationStrategy.GetConcurrencyValue() | ||||||
|  | 	workers := make([]worker, 0, concurrency) | ||||||
|  |  | ||||||
|  | 	address := h.receiverConfig.Listen.AsAddress() | ||||||
|  | 	if address == nil { | ||||||
|  | 		address = net.AnyIP | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	uplinkCounter, downlinkCounter := getStatCounter(h.v, h.tag) | ||||||
|  |  | ||||||
|  | 	for i := uint32(0); i < concurrency; i++ { | ||||||
|  | 		port := h.allocatePort() | ||||||
|  | 		rawProxy, err := core.CreateObject(h.v, h.proxyConfig) | ||||||
|  | 		if err != nil { | ||||||
|  | 			newError("failed to create proxy instance").Base(err).AtWarning().WriteToLog() | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		p := rawProxy.(proxy.Inbound) | ||||||
|  | 		nl := p.Network() | ||||||
|  | 		if net.HasNetwork(nl, net.Network_TCP) { | ||||||
|  | 			worker := &tcpWorker{ | ||||||
|  | 				tag:             h.tag, | ||||||
|  | 				address:         address, | ||||||
|  | 				port:            port, | ||||||
|  | 				proxy:           p, | ||||||
|  | 				stream:          h.streamSettings, | ||||||
|  | 				recvOrigDest:    h.receiverConfig.ReceiveOriginalDestination, | ||||||
|  | 				dispatcher:      h.mux, | ||||||
|  | 				sniffingConfig:  h.receiverConfig.GetEffectiveSniffingSettings(), | ||||||
|  | 				uplinkCounter:   uplinkCounter, | ||||||
|  | 				downlinkCounter: downlinkCounter, | ||||||
|  | 				ctx:             h.ctx, | ||||||
|  | 			} | ||||||
|  | 			if err := worker.Start(); err != nil { | ||||||
|  | 				newError("failed to create TCP worker").Base(err).AtWarning().WriteToLog() | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			workers = append(workers, worker) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if net.HasNetwork(nl, net.Network_UDP) { | ||||||
|  | 			worker := &udpWorker{ | ||||||
|  | 				tag:             h.tag, | ||||||
|  | 				proxy:           p, | ||||||
|  | 				address:         address, | ||||||
|  | 				port:            port, | ||||||
|  | 				dispatcher:      h.mux, | ||||||
|  | 				uplinkCounter:   uplinkCounter, | ||||||
|  | 				downlinkCounter: downlinkCounter, | ||||||
|  | 				stream:          h.streamSettings, | ||||||
|  | 			} | ||||||
|  | 			if err := worker.Start(); err != nil { | ||||||
|  | 				newError("failed to create UDP worker").Base(err).AtWarning().WriteToLog() | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			workers = append(workers, worker) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	h.workerMutex.Lock() | ||||||
|  | 	h.worker = workers | ||||||
|  | 	h.workerMutex.Unlock() | ||||||
|  |  | ||||||
|  | 	time.AfterFunc(timeout, func() { | ||||||
|  | 		h.closeWorkers(workers) | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (h *DynamicInboundHandler) Start() error { | ||||||
|  | 	return h.task.Start() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (h *DynamicInboundHandler) Close() error { | ||||||
|  | 	return h.task.Close() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (h *DynamicInboundHandler) GetRandomInboundProxy() (interface{}, net.Port, int) { | ||||||
|  | 	h.workerMutex.RLock() | ||||||
|  | 	defer h.workerMutex.RUnlock() | ||||||
|  |  | ||||||
|  | 	if len(h.worker) == 0 { | ||||||
|  | 		return nil, 0, 0 | ||||||
|  | 	} | ||||||
|  | 	w := h.worker[dice.Roll(len(h.worker))] | ||||||
|  | 	expire := h.receiverConfig.AllocationStrategy.GetRefreshValue() - uint32(time.Since(h.lastRefresh)/time.Minute) | ||||||
|  | 	return w.Proxy(), w.Port(), int(expire) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (h *DynamicInboundHandler) Tag() string { | ||||||
|  | 	return h.tag | ||||||
|  | } | ||||||
							
								
								
									
										9
									
								
								app/proxyman/inbound/errors.generated.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								app/proxyman/inbound/errors.generated.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | package inbound | ||||||
|  |  | ||||||
|  | import "github.com/xtls/xray-core/v1/common/errors" | ||||||
|  |  | ||||||
|  | type errPathObjHolder struct{} | ||||||
|  |  | ||||||
|  | func newError(values ...interface{}) *errors.Error { | ||||||
|  | 	return errors.New(values...).WithPathObj(errPathObjHolder{}) | ||||||
|  | } | ||||||
							
								
								
									
										178
									
								
								app/proxyman/inbound/inbound.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								app/proxyman/inbound/inbound.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,178 @@ | |||||||
|  | package inbound | ||||||
|  |  | ||||||
|  | //go:generate go run github.com/xtls/xray-core/v1/common/errors/errorgen | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"sync" | ||||||
|  |  | ||||||
|  | 	"github.com/xtls/xray-core/v1/app/proxyman" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/serial" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/session" | ||||||
|  | 	"github.com/xtls/xray-core/v1/core" | ||||||
|  | 	"github.com/xtls/xray-core/v1/features/inbound" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Manager is to manage all inbound handlers. | ||||||
|  | type Manager struct { | ||||||
|  | 	access          sync.RWMutex | ||||||
|  | 	untaggedHandler []inbound.Handler | ||||||
|  | 	taggedHandlers  map[string]inbound.Handler | ||||||
|  | 	running         bool | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // New returns a new Manager for inbound handlers. | ||||||
|  | func New(ctx context.Context, config *proxyman.InboundConfig) (*Manager, error) { | ||||||
|  | 	m := &Manager{ | ||||||
|  | 		taggedHandlers: make(map[string]inbound.Handler), | ||||||
|  | 	} | ||||||
|  | 	return m, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Type implements common.HasType. | ||||||
|  | func (*Manager) Type() interface{} { | ||||||
|  | 	return inbound.ManagerType() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // AddHandler implements inbound.Manager. | ||||||
|  | func (m *Manager) AddHandler(ctx context.Context, handler inbound.Handler) error { | ||||||
|  | 	m.access.Lock() | ||||||
|  | 	defer m.access.Unlock() | ||||||
|  |  | ||||||
|  | 	tag := handler.Tag() | ||||||
|  | 	if len(tag) > 0 { | ||||||
|  | 		m.taggedHandlers[tag] = handler | ||||||
|  | 	} else { | ||||||
|  | 		m.untaggedHandler = append(m.untaggedHandler, handler) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if m.running { | ||||||
|  | 		return handler.Start() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // GetHandler implements inbound.Manager. | ||||||
|  | func (m *Manager) GetHandler(ctx context.Context, tag string) (inbound.Handler, error) { | ||||||
|  | 	m.access.RLock() | ||||||
|  | 	defer m.access.RUnlock() | ||||||
|  |  | ||||||
|  | 	handler, found := m.taggedHandlers[tag] | ||||||
|  | 	if !found { | ||||||
|  | 		return nil, newError("handler not found: ", tag) | ||||||
|  | 	} | ||||||
|  | 	return handler, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // RemoveHandler implements inbound.Manager. | ||||||
|  | func (m *Manager) RemoveHandler(ctx context.Context, tag string) error { | ||||||
|  | 	if tag == "" { | ||||||
|  | 		return common.ErrNoClue | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	m.access.Lock() | ||||||
|  | 	defer m.access.Unlock() | ||||||
|  |  | ||||||
|  | 	if handler, found := m.taggedHandlers[tag]; found { | ||||||
|  | 		if err := handler.Close(); err != nil { | ||||||
|  | 			newError("failed to close handler ", tag).Base(err).AtWarning().WriteToLog(session.ExportIDToError(ctx)) | ||||||
|  | 		} | ||||||
|  | 		delete(m.taggedHandlers, tag) | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return common.ErrNoClue | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Start implements common.Runnable. | ||||||
|  | func (m *Manager) Start() error { | ||||||
|  | 	m.access.Lock() | ||||||
|  | 	defer m.access.Unlock() | ||||||
|  |  | ||||||
|  | 	m.running = true | ||||||
|  |  | ||||||
|  | 	for _, handler := range m.taggedHandlers { | ||||||
|  | 		if err := handler.Start(); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, handler := range m.untaggedHandler { | ||||||
|  | 		if err := handler.Start(); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Close implements common.Closable. | ||||||
|  | func (m *Manager) Close() error { | ||||||
|  | 	m.access.Lock() | ||||||
|  | 	defer m.access.Unlock() | ||||||
|  |  | ||||||
|  | 	m.running = false | ||||||
|  |  | ||||||
|  | 	var errors []interface{} | ||||||
|  | 	for _, handler := range m.taggedHandlers { | ||||||
|  | 		if err := handler.Close(); err != nil { | ||||||
|  | 			errors = append(errors, err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	for _, handler := range m.untaggedHandler { | ||||||
|  | 		if err := handler.Close(); err != nil { | ||||||
|  | 			errors = append(errors, err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(errors) > 0 { | ||||||
|  | 		return newError("failed to close all handlers").Base(newError(serial.Concat(errors...))) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // NewHandler creates a new inbound.Handler based on the given config. | ||||||
|  | func NewHandler(ctx context.Context, config *core.InboundHandlerConfig) (inbound.Handler, error) { | ||||||
|  | 	rawReceiverSettings, err := config.ReceiverSettings.GetInstance() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	proxySettings, err := config.ProxySettings.GetInstance() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	tag := config.Tag | ||||||
|  |  | ||||||
|  | 	receiverSettings, ok := rawReceiverSettings.(*proxyman.ReceiverConfig) | ||||||
|  | 	if !ok { | ||||||
|  | 		return nil, newError("not a ReceiverConfig").AtError() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	streamSettings := receiverSettings.StreamSettings | ||||||
|  | 	if streamSettings != nil && streamSettings.SocketSettings != nil { | ||||||
|  | 		ctx = session.ContextWithSockopt(ctx, &session.Sockopt{ | ||||||
|  | 			Mark: streamSettings.SocketSettings.Mark, | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	allocStrategy := receiverSettings.AllocationStrategy | ||||||
|  | 	if allocStrategy == nil || allocStrategy.Type == proxyman.AllocationStrategy_Always { | ||||||
|  | 		return NewAlwaysOnInboundHandler(ctx, tag, receiverSettings, proxySettings) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if allocStrategy.Type == proxyman.AllocationStrategy_Random { | ||||||
|  | 		return NewDynamicInboundHandler(ctx, tag, receiverSettings, proxySettings) | ||||||
|  | 	} | ||||||
|  | 	return nil, newError("unknown allocation strategy: ", receiverSettings.AllocationStrategy.Type).AtError() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func init() { | ||||||
|  | 	common.Must(common.RegisterConfig((*proxyman.InboundConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { | ||||||
|  | 		return New(ctx, config.(*proxyman.InboundConfig)) | ||||||
|  | 	})) | ||||||
|  | 	common.Must(common.RegisterConfig((*core.InboundHandlerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { | ||||||
|  | 		return NewHandler(ctx, config.(*core.InboundHandlerConfig)) | ||||||
|  | 	})) | ||||||
|  | } | ||||||
							
								
								
									
										483
									
								
								app/proxyman/inbound/worker.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										483
									
								
								app/proxyman/inbound/worker.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,483 @@ | |||||||
|  | package inbound | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"sync" | ||||||
|  | 	"sync/atomic" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/xtls/xray-core/v1/app/proxyman" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/buf" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/net" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/serial" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/session" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/signal/done" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/task" | ||||||
|  | 	"github.com/xtls/xray-core/v1/features/routing" | ||||||
|  | 	"github.com/xtls/xray-core/v1/features/stats" | ||||||
|  | 	"github.com/xtls/xray-core/v1/proxy" | ||||||
|  | 	"github.com/xtls/xray-core/v1/transport/internet" | ||||||
|  | 	"github.com/xtls/xray-core/v1/transport/internet/tcp" | ||||||
|  | 	"github.com/xtls/xray-core/v1/transport/internet/udp" | ||||||
|  | 	"github.com/xtls/xray-core/v1/transport/pipe" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type worker interface { | ||||||
|  | 	Start() error | ||||||
|  | 	Close() error | ||||||
|  | 	Port() net.Port | ||||||
|  | 	Proxy() proxy.Inbound | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type tcpWorker struct { | ||||||
|  | 	address         net.Address | ||||||
|  | 	port            net.Port | ||||||
|  | 	proxy           proxy.Inbound | ||||||
|  | 	stream          *internet.MemoryStreamConfig | ||||||
|  | 	recvOrigDest    bool | ||||||
|  | 	tag             string | ||||||
|  | 	dispatcher      routing.Dispatcher | ||||||
|  | 	sniffingConfig  *proxyman.SniffingConfig | ||||||
|  | 	uplinkCounter   stats.Counter | ||||||
|  | 	downlinkCounter stats.Counter | ||||||
|  |  | ||||||
|  | 	hub internet.Listener | ||||||
|  |  | ||||||
|  | 	ctx context.Context | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func getTProxyType(s *internet.MemoryStreamConfig) internet.SocketConfig_TProxyMode { | ||||||
|  | 	if s == nil || s.SocketSettings == nil { | ||||||
|  | 		return internet.SocketConfig_Off | ||||||
|  | 	} | ||||||
|  | 	return s.SocketSettings.Tproxy | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (w *tcpWorker) callback(conn internet.Connection) { | ||||||
|  | 	ctx, cancel := context.WithCancel(w.ctx) | ||||||
|  | 	sid := session.NewID() | ||||||
|  | 	ctx = session.ContextWithID(ctx, sid) | ||||||
|  |  | ||||||
|  | 	if w.recvOrigDest { | ||||||
|  | 		var dest net.Destination | ||||||
|  | 		switch getTProxyType(w.stream) { | ||||||
|  | 		case internet.SocketConfig_Redirect: | ||||||
|  | 			d, err := tcp.GetOriginalDestination(conn) | ||||||
|  | 			if err != nil { | ||||||
|  | 				newError("failed to get original destination").Base(err).WriteToLog(session.ExportIDToError(ctx)) | ||||||
|  | 			} else { | ||||||
|  | 				dest = d | ||||||
|  | 			} | ||||||
|  | 		case internet.SocketConfig_TProxy: | ||||||
|  | 			dest = net.DestinationFromAddr(conn.LocalAddr()) | ||||||
|  | 		} | ||||||
|  | 		if dest.IsValid() { | ||||||
|  | 			ctx = session.ContextWithOutbound(ctx, &session.Outbound{ | ||||||
|  | 				Target: dest, | ||||||
|  | 			}) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	ctx = session.ContextWithInbound(ctx, &session.Inbound{ | ||||||
|  | 		Source:  net.DestinationFromAddr(conn.RemoteAddr()), | ||||||
|  | 		Gateway: net.TCPDestination(w.address, w.port), | ||||||
|  | 		Tag:     w.tag, | ||||||
|  | 	}) | ||||||
|  | 	content := new(session.Content) | ||||||
|  | 	if w.sniffingConfig != nil { | ||||||
|  | 		content.SniffingRequest.Enabled = w.sniffingConfig.Enabled | ||||||
|  | 		content.SniffingRequest.OverrideDestinationForProtocol = w.sniffingConfig.DestinationOverride | ||||||
|  | 	} | ||||||
|  | 	ctx = session.ContextWithContent(ctx, content) | ||||||
|  | 	if w.uplinkCounter != nil || w.downlinkCounter != nil { | ||||||
|  | 		conn = &internet.StatCouterConnection{ | ||||||
|  | 			Connection:   conn, | ||||||
|  | 			ReadCounter:  w.uplinkCounter, | ||||||
|  | 			WriteCounter: w.downlinkCounter, | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if err := w.proxy.Process(ctx, net.Network_TCP, conn, w.dispatcher); err != nil { | ||||||
|  | 		newError("connection ends").Base(err).WriteToLog(session.ExportIDToError(ctx)) | ||||||
|  | 	} | ||||||
|  | 	cancel() | ||||||
|  | 	if err := conn.Close(); err != nil { | ||||||
|  | 		newError("failed to close connection").Base(err).WriteToLog(session.ExportIDToError(ctx)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (w *tcpWorker) Proxy() proxy.Inbound { | ||||||
|  | 	return w.proxy | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (w *tcpWorker) Start() error { | ||||||
|  | 	ctx := context.Background() | ||||||
|  | 	hub, err := internet.ListenTCP(ctx, w.address, w.port, w.stream, func(conn internet.Connection) { | ||||||
|  | 		go w.callback(conn) | ||||||
|  | 	}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return newError("failed to listen TCP on ", w.port).AtWarning().Base(err) | ||||||
|  | 	} | ||||||
|  | 	w.hub = hub | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (w *tcpWorker) Close() error { | ||||||
|  | 	var errors []interface{} | ||||||
|  | 	if w.hub != nil { | ||||||
|  | 		if err := common.Close(w.hub); err != nil { | ||||||
|  | 			errors = append(errors, err) | ||||||
|  | 		} | ||||||
|  | 		if err := common.Close(w.proxy); err != nil { | ||||||
|  | 			errors = append(errors, err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if len(errors) > 0 { | ||||||
|  | 		return newError("failed to close all resources").Base(newError(serial.Concat(errors...))) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (w *tcpWorker) Port() net.Port { | ||||||
|  | 	return w.port | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type udpConn struct { | ||||||
|  | 	lastActivityTime int64 // in seconds | ||||||
|  | 	reader           buf.Reader | ||||||
|  | 	writer           buf.Writer | ||||||
|  | 	output           func([]byte) (int, error) | ||||||
|  | 	remote           net.Addr | ||||||
|  | 	local            net.Addr | ||||||
|  | 	done             *done.Instance | ||||||
|  | 	uplink           stats.Counter | ||||||
|  | 	downlink         stats.Counter | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *udpConn) updateActivity() { | ||||||
|  | 	atomic.StoreInt64(&c.lastActivityTime, time.Now().Unix()) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ReadMultiBuffer implements buf.Reader | ||||||
|  | func (c *udpConn) ReadMultiBuffer() (buf.MultiBuffer, error) { | ||||||
|  | 	mb, err := c.reader.ReadMultiBuffer() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	c.updateActivity() | ||||||
|  |  | ||||||
|  | 	if c.uplink != nil { | ||||||
|  | 		c.uplink.Add(int64(mb.Len())) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return mb, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *udpConn) Read(buf []byte) (int, error) { | ||||||
|  | 	panic("not implemented") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Write implements io.Writer. | ||||||
|  | func (c *udpConn) Write(buf []byte) (int, error) { | ||||||
|  | 	n, err := c.output(buf) | ||||||
|  | 	if c.downlink != nil { | ||||||
|  | 		c.downlink.Add(int64(n)) | ||||||
|  | 	} | ||||||
|  | 	if err == nil { | ||||||
|  | 		c.updateActivity() | ||||||
|  | 	} | ||||||
|  | 	return n, err | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *udpConn) Close() error { | ||||||
|  | 	common.Must(c.done.Close()) | ||||||
|  | 	common.Must(common.Close(c.writer)) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *udpConn) RemoteAddr() net.Addr { | ||||||
|  | 	return c.remote | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *udpConn) LocalAddr() net.Addr { | ||||||
|  | 	return c.local | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (*udpConn) SetDeadline(time.Time) error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (*udpConn) SetReadDeadline(time.Time) error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (*udpConn) SetWriteDeadline(time.Time) error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type connID struct { | ||||||
|  | 	src  net.Destination | ||||||
|  | 	dest net.Destination | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type udpWorker struct { | ||||||
|  | 	sync.RWMutex | ||||||
|  |  | ||||||
|  | 	proxy           proxy.Inbound | ||||||
|  | 	hub             *udp.Hub | ||||||
|  | 	address         net.Address | ||||||
|  | 	port            net.Port | ||||||
|  | 	tag             string | ||||||
|  | 	stream          *internet.MemoryStreamConfig | ||||||
|  | 	dispatcher      routing.Dispatcher | ||||||
|  | 	uplinkCounter   stats.Counter | ||||||
|  | 	downlinkCounter stats.Counter | ||||||
|  |  | ||||||
|  | 	checker    *task.Periodic | ||||||
|  | 	activeConn map[connID]*udpConn | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (w *udpWorker) getConnection(id connID) (*udpConn, bool) { | ||||||
|  | 	w.Lock() | ||||||
|  | 	defer w.Unlock() | ||||||
|  |  | ||||||
|  | 	if conn, found := w.activeConn[id]; found && !conn.done.Done() { | ||||||
|  | 		return conn, true | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	pReader, pWriter := pipe.New(pipe.DiscardOverflow(), pipe.WithSizeLimit(16*1024)) | ||||||
|  | 	conn := &udpConn{ | ||||||
|  | 		reader: pReader, | ||||||
|  | 		writer: pWriter, | ||||||
|  | 		output: func(b []byte) (int, error) { | ||||||
|  | 			return w.hub.WriteTo(b, id.src) | ||||||
|  | 		}, | ||||||
|  | 		remote: &net.UDPAddr{ | ||||||
|  | 			IP:   id.src.Address.IP(), | ||||||
|  | 			Port: int(id.src.Port), | ||||||
|  | 		}, | ||||||
|  | 		local: &net.UDPAddr{ | ||||||
|  | 			IP:   w.address.IP(), | ||||||
|  | 			Port: int(w.port), | ||||||
|  | 		}, | ||||||
|  | 		done:     done.New(), | ||||||
|  | 		uplink:   w.uplinkCounter, | ||||||
|  | 		downlink: w.downlinkCounter, | ||||||
|  | 	} | ||||||
|  | 	w.activeConn[id] = conn | ||||||
|  |  | ||||||
|  | 	conn.updateActivity() | ||||||
|  | 	return conn, false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (w *udpWorker) callback(b *buf.Buffer, source net.Destination, originalDest net.Destination) { | ||||||
|  | 	id := connID{ | ||||||
|  | 		src: source, | ||||||
|  | 	} | ||||||
|  | 	if originalDest.IsValid() { | ||||||
|  | 		id.dest = originalDest | ||||||
|  | 	} | ||||||
|  | 	conn, existing := w.getConnection(id) | ||||||
|  |  | ||||||
|  | 	// payload will be discarded in pipe is full. | ||||||
|  | 	conn.writer.WriteMultiBuffer(buf.MultiBuffer{b}) | ||||||
|  |  | ||||||
|  | 	if !existing { | ||||||
|  | 		common.Must(w.checker.Start()) | ||||||
|  |  | ||||||
|  | 		go func() { | ||||||
|  | 			ctx := context.Background() | ||||||
|  | 			sid := session.NewID() | ||||||
|  | 			ctx = session.ContextWithID(ctx, sid) | ||||||
|  |  | ||||||
|  | 			if originalDest.IsValid() { | ||||||
|  | 				ctx = session.ContextWithOutbound(ctx, &session.Outbound{ | ||||||
|  | 					Target: originalDest, | ||||||
|  | 				}) | ||||||
|  | 			} | ||||||
|  | 			ctx = session.ContextWithInbound(ctx, &session.Inbound{ | ||||||
|  | 				Source:  source, | ||||||
|  | 				Gateway: net.UDPDestination(w.address, w.port), | ||||||
|  | 				Tag:     w.tag, | ||||||
|  | 			}) | ||||||
|  | 			if err := w.proxy.Process(ctx, net.Network_UDP, conn, w.dispatcher); err != nil { | ||||||
|  | 				newError("connection ends").Base(err).WriteToLog(session.ExportIDToError(ctx)) | ||||||
|  | 			} | ||||||
|  | 			conn.Close() | ||||||
|  | 			w.removeConn(id) | ||||||
|  | 		}() | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (w *udpWorker) removeConn(id connID) { | ||||||
|  | 	w.Lock() | ||||||
|  | 	delete(w.activeConn, id) | ||||||
|  | 	w.Unlock() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (w *udpWorker) handlePackets() { | ||||||
|  | 	receive := w.hub.Receive() | ||||||
|  | 	for payload := range receive { | ||||||
|  | 		w.callback(payload.Payload, payload.Source, payload.Target) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (w *udpWorker) clean() error { | ||||||
|  | 	nowSec := time.Now().Unix() | ||||||
|  | 	w.Lock() | ||||||
|  | 	defer w.Unlock() | ||||||
|  |  | ||||||
|  | 	if len(w.activeConn) == 0 { | ||||||
|  | 		return newError("no more connections. stopping...") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for addr, conn := range w.activeConn { | ||||||
|  | 		if nowSec-atomic.LoadInt64(&conn.lastActivityTime) > 8 { // TODO Timeout too small | ||||||
|  | 			delete(w.activeConn, addr) | ||||||
|  | 			conn.Close() | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(w.activeConn) == 0 { | ||||||
|  | 		w.activeConn = make(map[connID]*udpConn, 16) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (w *udpWorker) Start() error { | ||||||
|  | 	w.activeConn = make(map[connID]*udpConn, 16) | ||||||
|  | 	ctx := context.Background() | ||||||
|  | 	h, err := udp.ListenUDP(ctx, w.address, w.port, w.stream, udp.HubCapacity(256)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	w.checker = &task.Periodic{ | ||||||
|  | 		Interval: time.Second * 16, | ||||||
|  | 		Execute:  w.clean, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	w.hub = h | ||||||
|  | 	go w.handlePackets() | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (w *udpWorker) Close() error { | ||||||
|  | 	w.Lock() | ||||||
|  | 	defer w.Unlock() | ||||||
|  |  | ||||||
|  | 	var errors []interface{} | ||||||
|  |  | ||||||
|  | 	if w.hub != nil { | ||||||
|  | 		if err := w.hub.Close(); err != nil { | ||||||
|  | 			errors = append(errors, err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if w.checker != nil { | ||||||
|  | 		if err := w.checker.Close(); err != nil { | ||||||
|  | 			errors = append(errors, err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := common.Close(w.proxy); err != nil { | ||||||
|  | 		errors = append(errors, err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(errors) > 0 { | ||||||
|  | 		return newError("failed to close all resources").Base(newError(serial.Concat(errors...))) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (w *udpWorker) Port() net.Port { | ||||||
|  | 	return w.port | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (w *udpWorker) Proxy() proxy.Inbound { | ||||||
|  | 	return w.proxy | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type dsWorker struct { | ||||||
|  | 	address         net.Address | ||||||
|  | 	proxy           proxy.Inbound | ||||||
|  | 	stream          *internet.MemoryStreamConfig | ||||||
|  | 	tag             string | ||||||
|  | 	dispatcher      routing.Dispatcher | ||||||
|  | 	sniffingConfig  *proxyman.SniffingConfig | ||||||
|  | 	uplinkCounter   stats.Counter | ||||||
|  | 	downlinkCounter stats.Counter | ||||||
|  |  | ||||||
|  | 	hub internet.Listener | ||||||
|  |  | ||||||
|  | 	ctx context.Context | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (w *dsWorker) callback(conn internet.Connection) { | ||||||
|  | 	ctx, cancel := context.WithCancel(w.ctx) | ||||||
|  | 	sid := session.NewID() | ||||||
|  | 	ctx = session.ContextWithID(ctx, sid) | ||||||
|  |  | ||||||
|  | 	ctx = session.ContextWithInbound(ctx, &session.Inbound{ | ||||||
|  | 		Source:  net.DestinationFromAddr(conn.RemoteAddr()), | ||||||
|  | 		Gateway: net.UnixDestination(w.address), | ||||||
|  | 		Tag:     w.tag, | ||||||
|  | 	}) | ||||||
|  | 	content := new(session.Content) | ||||||
|  | 	if w.sniffingConfig != nil { | ||||||
|  | 		content.SniffingRequest.Enabled = w.sniffingConfig.Enabled | ||||||
|  | 		content.SniffingRequest.OverrideDestinationForProtocol = w.sniffingConfig.DestinationOverride | ||||||
|  | 	} | ||||||
|  | 	ctx = session.ContextWithContent(ctx, content) | ||||||
|  | 	if w.uplinkCounter != nil || w.downlinkCounter != nil { | ||||||
|  | 		conn = &internet.StatCouterConnection{ | ||||||
|  | 			Connection:   conn, | ||||||
|  | 			ReadCounter:  w.uplinkCounter, | ||||||
|  | 			WriteCounter: w.downlinkCounter, | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if err := w.proxy.Process(ctx, net.Network_UNIX, conn, w.dispatcher); err != nil { | ||||||
|  | 		newError("connection ends").Base(err).WriteToLog(session.ExportIDToError(ctx)) | ||||||
|  | 	} | ||||||
|  | 	cancel() | ||||||
|  | 	if err := conn.Close(); err != nil { | ||||||
|  | 		newError("failed to close connection").Base(err).WriteToLog(session.ExportIDToError(ctx)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (w *dsWorker) Proxy() proxy.Inbound { | ||||||
|  | 	return w.proxy | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (w *dsWorker) Port() net.Port { | ||||||
|  | 	return net.Port(0) | ||||||
|  | } | ||||||
|  | func (w *dsWorker) Start() error { | ||||||
|  | 	ctx := context.Background() | ||||||
|  | 	hub, err := internet.ListenUnix(ctx, w.address, w.stream, func(conn internet.Connection) { | ||||||
|  | 		go w.callback(conn) | ||||||
|  | 	}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return newError("failed to listen Unix Domain Socket on ", w.address).AtWarning().Base(err) | ||||||
|  | 	} | ||||||
|  | 	w.hub = hub | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (w *dsWorker) Close() error { | ||||||
|  | 	var errors []interface{} | ||||||
|  | 	if w.hub != nil { | ||||||
|  | 		if err := common.Close(w.hub); err != nil { | ||||||
|  | 			errors = append(errors, err) | ||||||
|  | 		} | ||||||
|  | 		if err := common.Close(w.proxy); err != nil { | ||||||
|  | 			errors = append(errors, err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if len(errors) > 0 { | ||||||
|  | 		return newError("failed to close all resources").Base(newError(serial.Concat(errors...))) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
							
								
								
									
										9
									
								
								app/proxyman/outbound/errors.generated.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								app/proxyman/outbound/errors.generated.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | package outbound | ||||||
|  |  | ||||||
|  | import "github.com/xtls/xray-core/v1/common/errors" | ||||||
|  |  | ||||||
|  | type errPathObjHolder struct{} | ||||||
|  |  | ||||||
|  | func newError(values ...interface{}) *errors.Error { | ||||||
|  | 	return errors.New(values...).WithPathObj(errPathObjHolder{}) | ||||||
|  | } | ||||||
							
								
								
									
										228
									
								
								app/proxyman/outbound/handler.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										228
									
								
								app/proxyman/outbound/handler.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,228 @@ | |||||||
|  | package outbound | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  |  | ||||||
|  | 	"github.com/xtls/xray-core/v1/app/proxyman" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/mux" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/net" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/session" | ||||||
|  | 	"github.com/xtls/xray-core/v1/core" | ||||||
|  | 	"github.com/xtls/xray-core/v1/features/outbound" | ||||||
|  | 	"github.com/xtls/xray-core/v1/features/policy" | ||||||
|  | 	"github.com/xtls/xray-core/v1/features/stats" | ||||||
|  | 	"github.com/xtls/xray-core/v1/proxy" | ||||||
|  | 	"github.com/xtls/xray-core/v1/transport" | ||||||
|  | 	"github.com/xtls/xray-core/v1/transport/internet" | ||||||
|  | 	"github.com/xtls/xray-core/v1/transport/internet/tls" | ||||||
|  | 	"github.com/xtls/xray-core/v1/transport/pipe" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func getStatCounter(v *core.Instance, tag string) (stats.Counter, stats.Counter) { | ||||||
|  | 	var uplinkCounter stats.Counter | ||||||
|  | 	var downlinkCounter stats.Counter | ||||||
|  |  | ||||||
|  | 	policy := v.GetFeature(policy.ManagerType()).(policy.Manager) | ||||||
|  | 	if len(tag) > 0 && policy.ForSystem().Stats.OutboundUplink { | ||||||
|  | 		statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager) | ||||||
|  | 		name := "outbound>>>" + tag + ">>>traffic>>>uplink" | ||||||
|  | 		c, _ := stats.GetOrRegisterCounter(statsManager, name) | ||||||
|  | 		if c != nil { | ||||||
|  | 			uplinkCounter = c | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if len(tag) > 0 && policy.ForSystem().Stats.OutboundDownlink { | ||||||
|  | 		statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager) | ||||||
|  | 		name := "outbound>>>" + tag + ">>>traffic>>>downlink" | ||||||
|  | 		c, _ := stats.GetOrRegisterCounter(statsManager, name) | ||||||
|  | 		if c != nil { | ||||||
|  | 			downlinkCounter = c | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return uplinkCounter, downlinkCounter | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Handler is an implements of outbound.Handler. | ||||||
|  | type Handler struct { | ||||||
|  | 	tag             string | ||||||
|  | 	senderSettings  *proxyman.SenderConfig | ||||||
|  | 	streamSettings  *internet.MemoryStreamConfig | ||||||
|  | 	proxy           proxy.Outbound | ||||||
|  | 	outboundManager outbound.Manager | ||||||
|  | 	mux             *mux.ClientManager | ||||||
|  | 	uplinkCounter   stats.Counter | ||||||
|  | 	downlinkCounter stats.Counter | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // NewHandler create a new Handler based on the given configuration. | ||||||
|  | func NewHandler(ctx context.Context, config *core.OutboundHandlerConfig) (outbound.Handler, error) { | ||||||
|  | 	v := core.MustFromContext(ctx) | ||||||
|  | 	uplinkCounter, downlinkCounter := getStatCounter(v, config.Tag) | ||||||
|  | 	h := &Handler{ | ||||||
|  | 		tag:             config.Tag, | ||||||
|  | 		outboundManager: v.GetFeature(outbound.ManagerType()).(outbound.Manager), | ||||||
|  | 		uplinkCounter:   uplinkCounter, | ||||||
|  | 		downlinkCounter: downlinkCounter, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if config.SenderSettings != nil { | ||||||
|  | 		senderSettings, err := config.SenderSettings.GetInstance() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		switch s := senderSettings.(type) { | ||||||
|  | 		case *proxyman.SenderConfig: | ||||||
|  | 			h.senderSettings = s | ||||||
|  | 			mss, err := internet.ToMemoryStreamConfig(s.StreamSettings) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, newError("failed to parse stream settings").Base(err).AtWarning() | ||||||
|  | 			} | ||||||
|  | 			h.streamSettings = mss | ||||||
|  | 		default: | ||||||
|  | 			return nil, newError("settings is not SenderConfig") | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	proxyConfig, err := config.ProxySettings.GetInstance() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	rawProxyHandler, err := common.CreateObject(ctx, proxyConfig) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	proxyHandler, ok := rawProxyHandler.(proxy.Outbound) | ||||||
|  | 	if !ok { | ||||||
|  | 		return nil, newError("not an outbound handler") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if h.senderSettings != nil && h.senderSettings.MultiplexSettings != nil { | ||||||
|  | 		config := h.senderSettings.MultiplexSettings | ||||||
|  | 		if config.Concurrency < 1 || config.Concurrency > 1024 { | ||||||
|  | 			return nil, newError("invalid mux concurrency: ", config.Concurrency).AtWarning() | ||||||
|  | 		} | ||||||
|  | 		h.mux = &mux.ClientManager{ | ||||||
|  | 			Enabled: h.senderSettings.MultiplexSettings.Enabled, | ||||||
|  | 			Picker: &mux.IncrementalWorkerPicker{ | ||||||
|  | 				Factory: &mux.DialingWorkerFactory{ | ||||||
|  | 					Proxy:  proxyHandler, | ||||||
|  | 					Dialer: h, | ||||||
|  | 					Strategy: mux.ClientStrategy{ | ||||||
|  | 						MaxConcurrency: config.Concurrency, | ||||||
|  | 						MaxConnection:  128, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	h.proxy = proxyHandler | ||||||
|  | 	return h, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Tag implements outbound.Handler. | ||||||
|  | func (h *Handler) Tag() string { | ||||||
|  | 	return h.tag | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Dispatch implements proxy.Outbound.Dispatch. | ||||||
|  | func (h *Handler) Dispatch(ctx context.Context, link *transport.Link) { | ||||||
|  | 	if h.mux != nil && (h.mux.Enabled || session.MuxPreferedFromContext(ctx)) { | ||||||
|  | 		if err := h.mux.Dispatch(ctx, link); err != nil { | ||||||
|  | 			newError("failed to process mux outbound traffic").Base(err).WriteToLog(session.ExportIDToError(ctx)) | ||||||
|  | 			common.Interrupt(link.Writer) | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		if err := h.proxy.Process(ctx, link, h); err != nil { | ||||||
|  | 			// Ensure outbound ray is properly closed. | ||||||
|  | 			newError("failed to process outbound traffic").Base(err).WriteToLog(session.ExportIDToError(ctx)) | ||||||
|  | 			common.Interrupt(link.Writer) | ||||||
|  | 		} else { | ||||||
|  | 			common.Must(common.Close(link.Writer)) | ||||||
|  | 		} | ||||||
|  | 		common.Interrupt(link.Reader) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Address implements internet.Dialer. | ||||||
|  | func (h *Handler) Address() net.Address { | ||||||
|  | 	if h.senderSettings == nil || h.senderSettings.Via == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	return h.senderSettings.Via.AsAddress() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Dial implements internet.Dialer. | ||||||
|  | func (h *Handler) Dial(ctx context.Context, dest net.Destination) (internet.Connection, error) { | ||||||
|  | 	if h.senderSettings != nil { | ||||||
|  | 		if h.senderSettings.ProxySettings.HasTag() { | ||||||
|  | 			tag := h.senderSettings.ProxySettings.Tag | ||||||
|  | 			handler := h.outboundManager.GetHandler(tag) | ||||||
|  | 			if handler != nil { | ||||||
|  | 				newError("proxying to ", tag, " for dest ", dest).AtDebug().WriteToLog(session.ExportIDToError(ctx)) | ||||||
|  | 				ctx = session.ContextWithOutbound(ctx, &session.Outbound{ | ||||||
|  | 					Target: dest, | ||||||
|  | 				}) | ||||||
|  |  | ||||||
|  | 				opts := pipe.OptionsFromContext(ctx) | ||||||
|  | 				uplinkReader, uplinkWriter := pipe.New(opts...) | ||||||
|  | 				downlinkReader, downlinkWriter := pipe.New(opts...) | ||||||
|  |  | ||||||
|  | 				go handler.Dispatch(ctx, &transport.Link{Reader: uplinkReader, Writer: downlinkWriter}) | ||||||
|  | 				conn := net.NewConnection(net.ConnectionInputMulti(uplinkWriter), net.ConnectionOutputMulti(downlinkReader)) | ||||||
|  |  | ||||||
|  | 				if config := tls.ConfigFromStreamSettings(h.streamSettings); config != nil { | ||||||
|  | 					tlsConfig := config.GetTLSConfig(tls.WithDestination(dest)) | ||||||
|  | 					conn = tls.Client(conn, tlsConfig) | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				return h.getStatCouterConnection(conn), nil | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			newError("failed to get outbound handler with tag: ", tag).AtWarning().WriteToLog(session.ExportIDToError(ctx)) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if h.senderSettings.Via != nil { | ||||||
|  | 			outbound := session.OutboundFromContext(ctx) | ||||||
|  | 			if outbound == nil { | ||||||
|  | 				outbound = new(session.Outbound) | ||||||
|  | 				ctx = session.ContextWithOutbound(ctx, outbound) | ||||||
|  | 			} | ||||||
|  | 			outbound.Gateway = h.senderSettings.Via.AsAddress() | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	conn, err := internet.Dial(ctx, dest, h.streamSettings) | ||||||
|  | 	return h.getStatCouterConnection(conn), err | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (h *Handler) getStatCouterConnection(conn internet.Connection) internet.Connection { | ||||||
|  | 	if h.uplinkCounter != nil || h.downlinkCounter != nil { | ||||||
|  | 		return &internet.StatCouterConnection{ | ||||||
|  | 			Connection:   conn, | ||||||
|  | 			ReadCounter:  h.downlinkCounter, | ||||||
|  | 			WriteCounter: h.uplinkCounter, | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return conn | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // GetOutbound implements proxy.GetOutbound. | ||||||
|  | func (h *Handler) GetOutbound() proxy.Outbound { | ||||||
|  | 	return h.proxy | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Start implements common.Runnable. | ||||||
|  | func (h *Handler) Start() error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Close implements common.Closable. | ||||||
|  | func (h *Handler) Close() error { | ||||||
|  | 	common.Close(h.mux) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
							
								
								
									
										80
									
								
								app/proxyman/outbound/handler_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								app/proxyman/outbound/handler_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,80 @@ | |||||||
|  | package outbound_test | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"github.com/xtls/xray-core/v1/app/policy" | ||||||
|  | 	. "github.com/xtls/xray-core/v1/app/proxyman/outbound" | ||||||
|  | 	"github.com/xtls/xray-core/v1/app/stats" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/net" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/serial" | ||||||
|  | 	core "github.com/xtls/xray-core/v1/core" | ||||||
|  | 	"github.com/xtls/xray-core/v1/features/outbound" | ||||||
|  | 	"github.com/xtls/xray-core/v1/proxy/freedom" | ||||||
|  | 	"github.com/xtls/xray-core/v1/transport/internet" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestInterfaces(t *testing.T) { | ||||||
|  | 	_ = (outbound.Handler)(new(Handler)) | ||||||
|  | 	_ = (outbound.Manager)(new(Manager)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const xrayKey core.XrayKey = 1 | ||||||
|  |  | ||||||
|  | func TestOutboundWithoutStatCounter(t *testing.T) { | ||||||
|  | 	config := &core.Config{ | ||||||
|  | 		App: []*serial.TypedMessage{ | ||||||
|  | 			serial.ToTypedMessage(&stats.Config{}), | ||||||
|  | 			serial.ToTypedMessage(&policy.Config{ | ||||||
|  | 				System: &policy.SystemPolicy{ | ||||||
|  | 					Stats: &policy.SystemPolicy_Stats{ | ||||||
|  | 						InboundUplink: true, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}), | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	v, _ := core.New(config) | ||||||
|  | 	v.AddFeature((outbound.Manager)(new(Manager))) | ||||||
|  | 	ctx := context.WithValue(context.Background(), xrayKey, v) | ||||||
|  | 	h, _ := NewHandler(ctx, &core.OutboundHandlerConfig{ | ||||||
|  | 		Tag:           "tag", | ||||||
|  | 		ProxySettings: serial.ToTypedMessage(&freedom.Config{}), | ||||||
|  | 	}) | ||||||
|  | 	conn, _ := h.(*Handler).Dial(ctx, net.TCPDestination(net.DomainAddress("localhost"), 13146)) | ||||||
|  | 	_, ok := conn.(*internet.StatCouterConnection) | ||||||
|  | 	if ok { | ||||||
|  | 		t.Errorf("Expected conn to not be StatCouterConnection") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestOutboundWithStatCounter(t *testing.T) { | ||||||
|  | 	config := &core.Config{ | ||||||
|  | 		App: []*serial.TypedMessage{ | ||||||
|  | 			serial.ToTypedMessage(&stats.Config{}), | ||||||
|  | 			serial.ToTypedMessage(&policy.Config{ | ||||||
|  | 				System: &policy.SystemPolicy{ | ||||||
|  | 					Stats: &policy.SystemPolicy_Stats{ | ||||||
|  | 						OutboundUplink:   true, | ||||||
|  | 						OutboundDownlink: true, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}), | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	v, _ := core.New(config) | ||||||
|  | 	v.AddFeature((outbound.Manager)(new(Manager))) | ||||||
|  | 	ctx := context.WithValue(context.Background(), xrayKey, v) | ||||||
|  | 	h, _ := NewHandler(ctx, &core.OutboundHandlerConfig{ | ||||||
|  | 		Tag:           "tag", | ||||||
|  | 		ProxySettings: serial.ToTypedMessage(&freedom.Config{}), | ||||||
|  | 	}) | ||||||
|  | 	conn, _ := h.(*Handler).Dial(ctx, net.TCPDestination(net.DomainAddress("localhost"), 13146)) | ||||||
|  | 	_, ok := conn.(*internet.StatCouterConnection) | ||||||
|  | 	if !ok { | ||||||
|  | 		t.Errorf("Expected conn to be StatCouterConnection") | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										170
									
								
								app/proxyman/outbound/outbound.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								app/proxyman/outbound/outbound.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,170 @@ | |||||||
|  | package outbound | ||||||
|  |  | ||||||
|  | //go:generate go run github.com/xtls/xray-core/v1/common/errors/errorgen | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"strings" | ||||||
|  | 	"sync" | ||||||
|  |  | ||||||
|  | 	"github.com/xtls/xray-core/v1/app/proxyman" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/errors" | ||||||
|  | 	"github.com/xtls/xray-core/v1/core" | ||||||
|  | 	"github.com/xtls/xray-core/v1/features/outbound" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Manager is to manage all outbound handlers. | ||||||
|  | type Manager struct { | ||||||
|  | 	access           sync.RWMutex | ||||||
|  | 	defaultHandler   outbound.Handler | ||||||
|  | 	taggedHandler    map[string]outbound.Handler | ||||||
|  | 	untaggedHandlers []outbound.Handler | ||||||
|  | 	running          bool | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // New creates a new Manager. | ||||||
|  | func New(ctx context.Context, config *proxyman.OutboundConfig) (*Manager, error) { | ||||||
|  | 	m := &Manager{ | ||||||
|  | 		taggedHandler: make(map[string]outbound.Handler), | ||||||
|  | 	} | ||||||
|  | 	return m, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Type implements common.HasType. | ||||||
|  | func (m *Manager) Type() interface{} { | ||||||
|  | 	return outbound.ManagerType() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Start implements core.Feature | ||||||
|  | func (m *Manager) Start() error { | ||||||
|  | 	m.access.Lock() | ||||||
|  | 	defer m.access.Unlock() | ||||||
|  |  | ||||||
|  | 	m.running = true | ||||||
|  |  | ||||||
|  | 	for _, h := range m.taggedHandler { | ||||||
|  | 		if err := h.Start(); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, h := range m.untaggedHandlers { | ||||||
|  | 		if err := h.Start(); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Close implements core.Feature | ||||||
|  | func (m *Manager) Close() error { | ||||||
|  | 	m.access.Lock() | ||||||
|  | 	defer m.access.Unlock() | ||||||
|  |  | ||||||
|  | 	m.running = false | ||||||
|  |  | ||||||
|  | 	var errs []error | ||||||
|  | 	for _, h := range m.taggedHandler { | ||||||
|  | 		errs = append(errs, h.Close()) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, h := range m.untaggedHandlers { | ||||||
|  | 		errs = append(errs, h.Close()) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return errors.Combine(errs...) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // GetDefaultHandler implements outbound.Manager. | ||||||
|  | func (m *Manager) GetDefaultHandler() outbound.Handler { | ||||||
|  | 	m.access.RLock() | ||||||
|  | 	defer m.access.RUnlock() | ||||||
|  |  | ||||||
|  | 	if m.defaultHandler == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	return m.defaultHandler | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // GetHandler implements outbound.Manager. | ||||||
|  | func (m *Manager) GetHandler(tag string) outbound.Handler { | ||||||
|  | 	m.access.RLock() | ||||||
|  | 	defer m.access.RUnlock() | ||||||
|  | 	if handler, found := m.taggedHandler[tag]; found { | ||||||
|  | 		return handler | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // AddHandler implements outbound.Manager. | ||||||
|  | func (m *Manager) AddHandler(ctx context.Context, handler outbound.Handler) error { | ||||||
|  | 	m.access.Lock() | ||||||
|  | 	defer m.access.Unlock() | ||||||
|  |  | ||||||
|  | 	if m.defaultHandler == nil { | ||||||
|  | 		m.defaultHandler = handler | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	tag := handler.Tag() | ||||||
|  | 	if len(tag) > 0 { | ||||||
|  | 		m.taggedHandler[tag] = handler | ||||||
|  | 	} else { | ||||||
|  | 		m.untaggedHandlers = append(m.untaggedHandlers, handler) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if m.running { | ||||||
|  | 		return handler.Start() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // RemoveHandler implements outbound.Manager. | ||||||
|  | func (m *Manager) RemoveHandler(ctx context.Context, tag string) error { | ||||||
|  | 	if tag == "" { | ||||||
|  | 		return common.ErrNoClue | ||||||
|  | 	} | ||||||
|  | 	m.access.Lock() | ||||||
|  | 	defer m.access.Unlock() | ||||||
|  |  | ||||||
|  | 	delete(m.taggedHandler, tag) | ||||||
|  | 	if m.defaultHandler != nil && m.defaultHandler.Tag() == tag { | ||||||
|  | 		m.defaultHandler = nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Select implements outbound.HandlerSelector. | ||||||
|  | func (m *Manager) Select(selectors []string) []string { | ||||||
|  | 	m.access.RLock() | ||||||
|  | 	defer m.access.RUnlock() | ||||||
|  |  | ||||||
|  | 	tags := make([]string, 0, len(selectors)) | ||||||
|  |  | ||||||
|  | 	for tag := range m.taggedHandler { | ||||||
|  | 		match := false | ||||||
|  | 		for _, selector := range selectors { | ||||||
|  | 			if strings.HasPrefix(tag, selector) { | ||||||
|  | 				match = true | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		if match { | ||||||
|  | 			tags = append(tags, tag) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return tags | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func init() { | ||||||
|  | 	common.Must(common.RegisterConfig((*proxyman.OutboundConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { | ||||||
|  | 		return New(ctx, config.(*proxyman.OutboundConfig)) | ||||||
|  | 	})) | ||||||
|  | 	common.Must(common.RegisterConfig((*core.OutboundHandlerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { | ||||||
|  | 		return NewHandler(ctx, config.(*core.OutboundHandlerConfig)) | ||||||
|  | 	})) | ||||||
|  | } | ||||||
							
								
								
									
										194
									
								
								app/reverse/bridge.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										194
									
								
								app/reverse/bridge.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,194 @@ | |||||||
|  | // +build !confonly | ||||||
|  |  | ||||||
|  | package reverse | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/golang/protobuf/proto" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/mux" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/net" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/session" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/task" | ||||||
|  | 	"github.com/xtls/xray-core/v1/features/routing" | ||||||
|  | 	"github.com/xtls/xray-core/v1/transport" | ||||||
|  | 	"github.com/xtls/xray-core/v1/transport/pipe" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Bridge is a component in reverse proxy, that relays connections from Portal to local address. | ||||||
|  | type Bridge struct { | ||||||
|  | 	dispatcher  routing.Dispatcher | ||||||
|  | 	tag         string | ||||||
|  | 	domain      string | ||||||
|  | 	workers     []*BridgeWorker | ||||||
|  | 	monitorTask *task.Periodic | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // NewBridge creates a new Bridge instance. | ||||||
|  | func NewBridge(config *BridgeConfig, dispatcher routing.Dispatcher) (*Bridge, error) { | ||||||
|  | 	if config.Tag == "" { | ||||||
|  | 		return nil, newError("bridge tag is empty") | ||||||
|  | 	} | ||||||
|  | 	if config.Domain == "" { | ||||||
|  | 		return nil, newError("bridge domain is empty") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	b := &Bridge{ | ||||||
|  | 		dispatcher: dispatcher, | ||||||
|  | 		tag:        config.Tag, | ||||||
|  | 		domain:     config.Domain, | ||||||
|  | 	} | ||||||
|  | 	b.monitorTask = &task.Periodic{ | ||||||
|  | 		Execute:  b.monitor, | ||||||
|  | 		Interval: time.Second * 2, | ||||||
|  | 	} | ||||||
|  | 	return b, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Bridge) cleanup() { | ||||||
|  | 	var activeWorkers []*BridgeWorker | ||||||
|  |  | ||||||
|  | 	for _, w := range b.workers { | ||||||
|  | 		if w.IsActive() { | ||||||
|  | 			activeWorkers = append(activeWorkers, w) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(activeWorkers) != len(b.workers) { | ||||||
|  | 		b.workers = activeWorkers | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Bridge) monitor() error { | ||||||
|  | 	b.cleanup() | ||||||
|  |  | ||||||
|  | 	var numConnections uint32 | ||||||
|  | 	var numWorker uint32 | ||||||
|  |  | ||||||
|  | 	for _, w := range b.workers { | ||||||
|  | 		if w.IsActive() { | ||||||
|  | 			numConnections += w.Connections() | ||||||
|  | 			numWorker++ | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if numWorker == 0 || numConnections/numWorker > 16 { | ||||||
|  | 		worker, err := NewBridgeWorker(b.domain, b.tag, b.dispatcher) | ||||||
|  | 		if err != nil { | ||||||
|  | 			newError("failed to create bridge worker").Base(err).AtWarning().WriteToLog() | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  | 		b.workers = append(b.workers, worker) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Bridge) Start() error { | ||||||
|  | 	return b.monitorTask.Start() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Bridge) Close() error { | ||||||
|  | 	return b.monitorTask.Close() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type BridgeWorker struct { | ||||||
|  | 	tag        string | ||||||
|  | 	worker     *mux.ServerWorker | ||||||
|  | 	dispatcher routing.Dispatcher | ||||||
|  | 	state      Control_State | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewBridgeWorker(domain string, tag string, d routing.Dispatcher) (*BridgeWorker, error) { | ||||||
|  | 	ctx := context.Background() | ||||||
|  | 	ctx = session.ContextWithInbound(ctx, &session.Inbound{ | ||||||
|  | 		Tag: tag, | ||||||
|  | 	}) | ||||||
|  | 	link, err := d.Dispatch(ctx, net.Destination{ | ||||||
|  | 		Network: net.Network_TCP, | ||||||
|  | 		Address: net.DomainAddress(domain), | ||||||
|  | 		Port:    0, | ||||||
|  | 	}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	w := &BridgeWorker{ | ||||||
|  | 		dispatcher: d, | ||||||
|  | 		tag:        tag, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	worker, err := mux.NewServerWorker(context.Background(), w, link) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	w.worker = worker | ||||||
|  |  | ||||||
|  | 	return w, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (w *BridgeWorker) Type() interface{} { | ||||||
|  | 	return routing.DispatcherType() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (w *BridgeWorker) Start() error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (w *BridgeWorker) Close() error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (w *BridgeWorker) IsActive() bool { | ||||||
|  | 	return w.state == Control_ACTIVE && !w.worker.Closed() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (w *BridgeWorker) Connections() uint32 { | ||||||
|  | 	return w.worker.ActiveConnections() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (w *BridgeWorker) handleInternalConn(link transport.Link) { | ||||||
|  | 	go func() { | ||||||
|  | 		reader := link.Reader | ||||||
|  | 		for { | ||||||
|  | 			mb, err := reader.ReadMultiBuffer() | ||||||
|  | 			if err != nil { | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 			for _, b := range mb { | ||||||
|  | 				var ctl Control | ||||||
|  | 				if err := proto.Unmarshal(b.Bytes(), &ctl); err != nil { | ||||||
|  | 					newError("failed to parse proto message").Base(err).WriteToLog() | ||||||
|  | 					break | ||||||
|  | 				} | ||||||
|  | 				if ctl.State != w.state { | ||||||
|  | 					w.state = ctl.State | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (w *BridgeWorker) Dispatch(ctx context.Context, dest net.Destination) (*transport.Link, error) { | ||||||
|  | 	if !isInternalDomain(dest) { | ||||||
|  | 		ctx = session.ContextWithInbound(ctx, &session.Inbound{ | ||||||
|  | 			Tag: w.tag, | ||||||
|  | 		}) | ||||||
|  | 		return w.dispatcher.Dispatch(ctx, dest) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	opt := []pipe.Option{pipe.WithSizeLimit(16 * 1024)} | ||||||
|  | 	uplinkReader, uplinkWriter := pipe.New(opt...) | ||||||
|  | 	downlinkReader, downlinkWriter := pipe.New(opt...) | ||||||
|  |  | ||||||
|  | 	w.handleInternalConn(transport.Link{ | ||||||
|  | 		Reader: downlinkReader, | ||||||
|  | 		Writer: uplinkWriter, | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	return &transport.Link{ | ||||||
|  | 		Reader: uplinkReader, | ||||||
|  | 		Writer: downlinkWriter, | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
							
								
								
									
										16
									
								
								app/reverse/config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								app/reverse/config.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | |||||||
|  | // +build !confonly | ||||||
|  |  | ||||||
|  | package reverse | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"crypto/rand" | ||||||
|  | 	"io" | ||||||
|  |  | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/dice" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func (c *Control) FillInRandom() { | ||||||
|  | 	randomLength := dice.Roll(64) | ||||||
|  | 	c.Random = make([]byte, randomLength) | ||||||
|  | 	io.ReadFull(rand.Reader, c.Random) | ||||||
|  | } | ||||||
							
								
								
									
										439
									
								
								app/reverse/config.pb.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										439
									
								
								app/reverse/config.pb.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,439 @@ | |||||||
|  | // Code generated by protoc-gen-go. DO NOT EDIT. | ||||||
|  | // versions: | ||||||
|  | // 	protoc-gen-go v1.25.0 | ||||||
|  | // 	protoc        v3.14.0 | ||||||
|  | // source: app/reverse/config.proto | ||||||
|  |  | ||||||
|  | package reverse | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	proto "github.com/golang/protobuf/proto" | ||||||
|  | 	protoreflect "google.golang.org/protobuf/reflect/protoreflect" | ||||||
|  | 	protoimpl "google.golang.org/protobuf/runtime/protoimpl" | ||||||
|  | 	reflect "reflect" | ||||||
|  | 	sync "sync" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	// Verify that this generated code is sufficiently up-to-date. | ||||||
|  | 	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) | ||||||
|  | 	// Verify that runtime/protoimpl is sufficiently up-to-date. | ||||||
|  | 	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // This is a compile-time assertion that a sufficiently up-to-date version | ||||||
|  | // of the legacy proto package is being used. | ||||||
|  | const _ = proto.ProtoPackageIsVersion4 | ||||||
|  |  | ||||||
|  | type Control_State int32 | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	Control_ACTIVE Control_State = 0 | ||||||
|  | 	Control_DRAIN  Control_State = 1 | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Enum value maps for Control_State. | ||||||
|  | var ( | ||||||
|  | 	Control_State_name = map[int32]string{ | ||||||
|  | 		0: "ACTIVE", | ||||||
|  | 		1: "DRAIN", | ||||||
|  | 	} | ||||||
|  | 	Control_State_value = map[string]int32{ | ||||||
|  | 		"ACTIVE": 0, | ||||||
|  | 		"DRAIN":  1, | ||||||
|  | 	} | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func (x Control_State) Enum() *Control_State { | ||||||
|  | 	p := new(Control_State) | ||||||
|  | 	*p = x | ||||||
|  | 	return p | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x Control_State) String() string { | ||||||
|  | 	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (Control_State) Descriptor() protoreflect.EnumDescriptor { | ||||||
|  | 	return file_app_reverse_config_proto_enumTypes[0].Descriptor() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (Control_State) Type() protoreflect.EnumType { | ||||||
|  | 	return &file_app_reverse_config_proto_enumTypes[0] | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x Control_State) Number() protoreflect.EnumNumber { | ||||||
|  | 	return protoreflect.EnumNumber(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Deprecated: Use Control_State.Descriptor instead. | ||||||
|  | func (Control_State) EnumDescriptor() ([]byte, []int) { | ||||||
|  | 	return file_app_reverse_config_proto_rawDescGZIP(), []int{0, 0} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Control struct { | ||||||
|  | 	state         protoimpl.MessageState | ||||||
|  | 	sizeCache     protoimpl.SizeCache | ||||||
|  | 	unknownFields protoimpl.UnknownFields | ||||||
|  |  | ||||||
|  | 	State  Control_State `protobuf:"varint,1,opt,name=state,proto3,enum=xray.app.reverse.Control_State" json:"state,omitempty"` | ||||||
|  | 	Random []byte        `protobuf:"bytes,99,opt,name=random,proto3" json:"random,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Control) Reset() { | ||||||
|  | 	*x = Control{} | ||||||
|  | 	if protoimpl.UnsafeEnabled { | ||||||
|  | 		mi := &file_app_reverse_config_proto_msgTypes[0] | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		ms.StoreMessageInfo(mi) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Control) String() string { | ||||||
|  | 	return protoimpl.X.MessageStringOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (*Control) ProtoMessage() {} | ||||||
|  |  | ||||||
|  | func (x *Control) ProtoReflect() protoreflect.Message { | ||||||
|  | 	mi := &file_app_reverse_config_proto_msgTypes[0] | ||||||
|  | 	if protoimpl.UnsafeEnabled && x != nil { | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		if ms.LoadMessageInfo() == nil { | ||||||
|  | 			ms.StoreMessageInfo(mi) | ||||||
|  | 		} | ||||||
|  | 		return ms | ||||||
|  | 	} | ||||||
|  | 	return mi.MessageOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Deprecated: Use Control.ProtoReflect.Descriptor instead. | ||||||
|  | func (*Control) Descriptor() ([]byte, []int) { | ||||||
|  | 	return file_app_reverse_config_proto_rawDescGZIP(), []int{0} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Control) GetState() Control_State { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.State | ||||||
|  | 	} | ||||||
|  | 	return Control_ACTIVE | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Control) GetRandom() []byte { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Random | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type BridgeConfig struct { | ||||||
|  | 	state         protoimpl.MessageState | ||||||
|  | 	sizeCache     protoimpl.SizeCache | ||||||
|  | 	unknownFields protoimpl.UnknownFields | ||||||
|  |  | ||||||
|  | 	Tag    string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"` | ||||||
|  | 	Domain string `protobuf:"bytes,2,opt,name=domain,proto3" json:"domain,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *BridgeConfig) Reset() { | ||||||
|  | 	*x = BridgeConfig{} | ||||||
|  | 	if protoimpl.UnsafeEnabled { | ||||||
|  | 		mi := &file_app_reverse_config_proto_msgTypes[1] | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		ms.StoreMessageInfo(mi) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *BridgeConfig) String() string { | ||||||
|  | 	return protoimpl.X.MessageStringOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (*BridgeConfig) ProtoMessage() {} | ||||||
|  |  | ||||||
|  | func (x *BridgeConfig) ProtoReflect() protoreflect.Message { | ||||||
|  | 	mi := &file_app_reverse_config_proto_msgTypes[1] | ||||||
|  | 	if protoimpl.UnsafeEnabled && x != nil { | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		if ms.LoadMessageInfo() == nil { | ||||||
|  | 			ms.StoreMessageInfo(mi) | ||||||
|  | 		} | ||||||
|  | 		return ms | ||||||
|  | 	} | ||||||
|  | 	return mi.MessageOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Deprecated: Use BridgeConfig.ProtoReflect.Descriptor instead. | ||||||
|  | func (*BridgeConfig) Descriptor() ([]byte, []int) { | ||||||
|  | 	return file_app_reverse_config_proto_rawDescGZIP(), []int{1} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *BridgeConfig) GetTag() string { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Tag | ||||||
|  | 	} | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *BridgeConfig) GetDomain() string { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Domain | ||||||
|  | 	} | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type PortalConfig struct { | ||||||
|  | 	state         protoimpl.MessageState | ||||||
|  | 	sizeCache     protoimpl.SizeCache | ||||||
|  | 	unknownFields protoimpl.UnknownFields | ||||||
|  |  | ||||||
|  | 	Tag    string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"` | ||||||
|  | 	Domain string `protobuf:"bytes,2,opt,name=domain,proto3" json:"domain,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *PortalConfig) Reset() { | ||||||
|  | 	*x = PortalConfig{} | ||||||
|  | 	if protoimpl.UnsafeEnabled { | ||||||
|  | 		mi := &file_app_reverse_config_proto_msgTypes[2] | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		ms.StoreMessageInfo(mi) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *PortalConfig) String() string { | ||||||
|  | 	return protoimpl.X.MessageStringOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (*PortalConfig) ProtoMessage() {} | ||||||
|  |  | ||||||
|  | func (x *PortalConfig) ProtoReflect() protoreflect.Message { | ||||||
|  | 	mi := &file_app_reverse_config_proto_msgTypes[2] | ||||||
|  | 	if protoimpl.UnsafeEnabled && x != nil { | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		if ms.LoadMessageInfo() == nil { | ||||||
|  | 			ms.StoreMessageInfo(mi) | ||||||
|  | 		} | ||||||
|  | 		return ms | ||||||
|  | 	} | ||||||
|  | 	return mi.MessageOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Deprecated: Use PortalConfig.ProtoReflect.Descriptor instead. | ||||||
|  | func (*PortalConfig) Descriptor() ([]byte, []int) { | ||||||
|  | 	return file_app_reverse_config_proto_rawDescGZIP(), []int{2} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *PortalConfig) GetTag() string { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Tag | ||||||
|  | 	} | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *PortalConfig) GetDomain() string { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Domain | ||||||
|  | 	} | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Config struct { | ||||||
|  | 	state         protoimpl.MessageState | ||||||
|  | 	sizeCache     protoimpl.SizeCache | ||||||
|  | 	unknownFields protoimpl.UnknownFields | ||||||
|  |  | ||||||
|  | 	BridgeConfig []*BridgeConfig `protobuf:"bytes,1,rep,name=bridge_config,json=bridgeConfig,proto3" json:"bridge_config,omitempty"` | ||||||
|  | 	PortalConfig []*PortalConfig `protobuf:"bytes,2,rep,name=portal_config,json=portalConfig,proto3" json:"portal_config,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Config) Reset() { | ||||||
|  | 	*x = Config{} | ||||||
|  | 	if protoimpl.UnsafeEnabled { | ||||||
|  | 		mi := &file_app_reverse_config_proto_msgTypes[3] | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		ms.StoreMessageInfo(mi) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Config) String() string { | ||||||
|  | 	return protoimpl.X.MessageStringOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (*Config) ProtoMessage() {} | ||||||
|  |  | ||||||
|  | func (x *Config) ProtoReflect() protoreflect.Message { | ||||||
|  | 	mi := &file_app_reverse_config_proto_msgTypes[3] | ||||||
|  | 	if protoimpl.UnsafeEnabled && x != nil { | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		if ms.LoadMessageInfo() == nil { | ||||||
|  | 			ms.StoreMessageInfo(mi) | ||||||
|  | 		} | ||||||
|  | 		return ms | ||||||
|  | 	} | ||||||
|  | 	return mi.MessageOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Deprecated: Use Config.ProtoReflect.Descriptor instead. | ||||||
|  | func (*Config) Descriptor() ([]byte, []int) { | ||||||
|  | 	return file_app_reverse_config_proto_rawDescGZIP(), []int{3} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Config) GetBridgeConfig() []*BridgeConfig { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.BridgeConfig | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Config) GetPortalConfig() []*PortalConfig { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.PortalConfig | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var File_app_reverse_config_proto protoreflect.FileDescriptor | ||||||
|  |  | ||||||
|  | var file_app_reverse_config_proto_rawDesc = []byte{ | ||||||
|  | 	0x0a, 0x18, 0x61, 0x70, 0x70, 0x2f, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x2f, 0x63, 0x6f, | ||||||
|  | 	0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x78, 0x72, 0x61, 0x79, | ||||||
|  | 	0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x22, 0x78, 0x0a, 0x07, | ||||||
|  | 	0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x12, 0x35, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, | ||||||
|  | 	0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, | ||||||
|  | 	0x70, 0x2e, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, | ||||||
|  | 	0x6c, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x16, | ||||||
|  | 	0x0a, 0x06, 0x72, 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x18, 0x63, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, | ||||||
|  | 	0x72, 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x22, 0x1e, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, | ||||||
|  | 	0x0a, 0x0a, 0x06, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x44, | ||||||
|  | 	0x52, 0x41, 0x49, 0x4e, 0x10, 0x01, 0x22, 0x38, 0x0a, 0x0c, 0x42, 0x72, 0x69, 0x64, 0x67, 0x65, | ||||||
|  | 	0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, | ||||||
|  | 	0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, | ||||||
|  | 	0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, | ||||||
|  | 	0x22, 0x38, 0x0a, 0x0c, 0x50, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, | ||||||
|  | 	0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, | ||||||
|  | 	0x61, 0x67, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, | ||||||
|  | 	0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x22, 0x92, 0x01, 0x0a, 0x06, 0x43, | ||||||
|  | 	0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x43, 0x0a, 0x0d, 0x62, 0x72, 0x69, 0x64, 0x67, 0x65, 0x5f, | ||||||
|  | 	0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x78, | ||||||
|  | 	0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x2e, | ||||||
|  | 	0x42, 0x72, 0x69, 0x64, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0c, 0x62, 0x72, | ||||||
|  | 	0x69, 0x64, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x43, 0x0a, 0x0d, 0x70, 0x6f, | ||||||
|  | 	0x72, 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x03, 0x28, | ||||||
|  | 	0x0b, 0x32, 0x1e, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x65, 0x76, | ||||||
|  | 	0x65, 0x72, 0x73, 0x65, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, | ||||||
|  | 	0x67, 0x52, 0x0c, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, | ||||||
|  | 	0x59, 0x0a, 0x16, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, | ||||||
|  | 	0x79, 0x2e, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x50, 0x01, 0x5a, 0x28, 0x67, 0x69, 0x74, | ||||||
|  | 	0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, | ||||||
|  | 	0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x72, 0x65, | ||||||
|  | 	0x76, 0x65, 0x72, 0x73, 0x65, 0xaa, 0x02, 0x12, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, | ||||||
|  | 	0x78, 0x79, 0x2e, 0x52, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, | ||||||
|  | 	0x6f, 0x33, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var ( | ||||||
|  | 	file_app_reverse_config_proto_rawDescOnce sync.Once | ||||||
|  | 	file_app_reverse_config_proto_rawDescData = file_app_reverse_config_proto_rawDesc | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func file_app_reverse_config_proto_rawDescGZIP() []byte { | ||||||
|  | 	file_app_reverse_config_proto_rawDescOnce.Do(func() { | ||||||
|  | 		file_app_reverse_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_app_reverse_config_proto_rawDescData) | ||||||
|  | 	}) | ||||||
|  | 	return file_app_reverse_config_proto_rawDescData | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var file_app_reverse_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1) | ||||||
|  | var file_app_reverse_config_proto_msgTypes = make([]protoimpl.MessageInfo, 4) | ||||||
|  | var file_app_reverse_config_proto_goTypes = []interface{}{ | ||||||
|  | 	(Control_State)(0),   // 0: xray.app.reverse.Control.State | ||||||
|  | 	(*Control)(nil),      // 1: xray.app.reverse.Control | ||||||
|  | 	(*BridgeConfig)(nil), // 2: xray.app.reverse.BridgeConfig | ||||||
|  | 	(*PortalConfig)(nil), // 3: xray.app.reverse.PortalConfig | ||||||
|  | 	(*Config)(nil),       // 4: xray.app.reverse.Config | ||||||
|  | } | ||||||
|  | var file_app_reverse_config_proto_depIdxs = []int32{ | ||||||
|  | 	0, // 0: xray.app.reverse.Control.state:type_name -> xray.app.reverse.Control.State | ||||||
|  | 	2, // 1: xray.app.reverse.Config.bridge_config:type_name -> xray.app.reverse.BridgeConfig | ||||||
|  | 	3, // 2: xray.app.reverse.Config.portal_config:type_name -> xray.app.reverse.PortalConfig | ||||||
|  | 	3, // [3:3] is the sub-list for method output_type | ||||||
|  | 	3, // [3:3] is the sub-list for method input_type | ||||||
|  | 	3, // [3:3] is the sub-list for extension type_name | ||||||
|  | 	3, // [3:3] is the sub-list for extension extendee | ||||||
|  | 	0, // [0:3] is the sub-list for field type_name | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func init() { file_app_reverse_config_proto_init() } | ||||||
|  | func file_app_reverse_config_proto_init() { | ||||||
|  | 	if File_app_reverse_config_proto != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if !protoimpl.UnsafeEnabled { | ||||||
|  | 		file_app_reverse_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { | ||||||
|  | 			switch v := v.(*Control); i { | ||||||
|  | 			case 0: | ||||||
|  | 				return &v.state | ||||||
|  | 			case 1: | ||||||
|  | 				return &v.sizeCache | ||||||
|  | 			case 2: | ||||||
|  | 				return &v.unknownFields | ||||||
|  | 			default: | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		file_app_reverse_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { | ||||||
|  | 			switch v := v.(*BridgeConfig); i { | ||||||
|  | 			case 0: | ||||||
|  | 				return &v.state | ||||||
|  | 			case 1: | ||||||
|  | 				return &v.sizeCache | ||||||
|  | 			case 2: | ||||||
|  | 				return &v.unknownFields | ||||||
|  | 			default: | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		file_app_reverse_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { | ||||||
|  | 			switch v := v.(*PortalConfig); i { | ||||||
|  | 			case 0: | ||||||
|  | 				return &v.state | ||||||
|  | 			case 1: | ||||||
|  | 				return &v.sizeCache | ||||||
|  | 			case 2: | ||||||
|  | 				return &v.unknownFields | ||||||
|  | 			default: | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		file_app_reverse_config_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { | ||||||
|  | 			switch v := v.(*Config); i { | ||||||
|  | 			case 0: | ||||||
|  | 				return &v.state | ||||||
|  | 			case 1: | ||||||
|  | 				return &v.sizeCache | ||||||
|  | 			case 2: | ||||||
|  | 				return &v.unknownFields | ||||||
|  | 			default: | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	type x struct{} | ||||||
|  | 	out := protoimpl.TypeBuilder{ | ||||||
|  | 		File: protoimpl.DescBuilder{ | ||||||
|  | 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(), | ||||||
|  | 			RawDescriptor: file_app_reverse_config_proto_rawDesc, | ||||||
|  | 			NumEnums:      1, | ||||||
|  | 			NumMessages:   4, | ||||||
|  | 			NumExtensions: 0, | ||||||
|  | 			NumServices:   0, | ||||||
|  | 		}, | ||||||
|  | 		GoTypes:           file_app_reverse_config_proto_goTypes, | ||||||
|  | 		DependencyIndexes: file_app_reverse_config_proto_depIdxs, | ||||||
|  | 		EnumInfos:         file_app_reverse_config_proto_enumTypes, | ||||||
|  | 		MessageInfos:      file_app_reverse_config_proto_msgTypes, | ||||||
|  | 	}.Build() | ||||||
|  | 	File_app_reverse_config_proto = out.File | ||||||
|  | 	file_app_reverse_config_proto_rawDesc = nil | ||||||
|  | 	file_app_reverse_config_proto_goTypes = nil | ||||||
|  | 	file_app_reverse_config_proto_depIdxs = nil | ||||||
|  | } | ||||||
							
								
								
									
										32
									
								
								app/reverse/config.proto
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								app/reverse/config.proto
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | |||||||
|  | syntax = "proto3"; | ||||||
|  |  | ||||||
|  | package xray.app.reverse; | ||||||
|  | option csharp_namespace = "Xray.Proxy.Reverse"; | ||||||
|  | option go_package = "github.com/xtls/xray-core/v1/app/reverse"; | ||||||
|  | option java_package = "com.xray.proxy.reverse"; | ||||||
|  | option java_multiple_files = true; | ||||||
|  |  | ||||||
|  | message Control { | ||||||
|  |   enum State { | ||||||
|  |     ACTIVE = 0; | ||||||
|  |     DRAIN = 1; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   State state = 1; | ||||||
|  |   bytes random = 99; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message BridgeConfig { | ||||||
|  |   string tag = 1; | ||||||
|  |   string domain = 2; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message PortalConfig { | ||||||
|  |   string tag = 1; | ||||||
|  |   string domain = 2; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message Config { | ||||||
|  |   repeated BridgeConfig bridge_config = 1; | ||||||
|  |   repeated PortalConfig portal_config = 2; | ||||||
|  | } | ||||||
							
								
								
									
										9
									
								
								app/reverse/errors.generated.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								app/reverse/errors.generated.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | package reverse | ||||||
|  |  | ||||||
|  | import "github.com/xtls/xray-core/v1/common/errors" | ||||||
|  |  | ||||||
|  | type errPathObjHolder struct{} | ||||||
|  |  | ||||||
|  | func newError(values ...interface{}) *errors.Error { | ||||||
|  | 	return errors.New(values...).WithPathObj(errPathObjHolder{}) | ||||||
|  | } | ||||||
							
								
								
									
										266
									
								
								app/reverse/portal.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										266
									
								
								app/reverse/portal.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,266 @@ | |||||||
|  | // +build !confonly | ||||||
|  |  | ||||||
|  | package reverse | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"sync" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/golang/protobuf/proto" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/buf" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/mux" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/net" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/session" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/task" | ||||||
|  | 	"github.com/xtls/xray-core/v1/features/outbound" | ||||||
|  | 	"github.com/xtls/xray-core/v1/transport" | ||||||
|  | 	"github.com/xtls/xray-core/v1/transport/pipe" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type Portal struct { | ||||||
|  | 	ohm    outbound.Manager | ||||||
|  | 	tag    string | ||||||
|  | 	domain string | ||||||
|  | 	picker *StaticMuxPicker | ||||||
|  | 	client *mux.ClientManager | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewPortal(config *PortalConfig, ohm outbound.Manager) (*Portal, error) { | ||||||
|  | 	if config.Tag == "" { | ||||||
|  | 		return nil, newError("portal tag is empty") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if config.Domain == "" { | ||||||
|  | 		return nil, newError("portal domain is empty") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	picker, err := NewStaticMuxPicker() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return &Portal{ | ||||||
|  | 		ohm:    ohm, | ||||||
|  | 		tag:    config.Tag, | ||||||
|  | 		domain: config.Domain, | ||||||
|  | 		picker: picker, | ||||||
|  | 		client: &mux.ClientManager{ | ||||||
|  | 			Picker: picker, | ||||||
|  | 		}, | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (p *Portal) Start() error { | ||||||
|  | 	return p.ohm.AddHandler(context.Background(), &Outbound{ | ||||||
|  | 		portal: p, | ||||||
|  | 		tag:    p.tag, | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (p *Portal) Close() error { | ||||||
|  | 	return p.ohm.RemoveHandler(context.Background(), p.tag) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (p *Portal) HandleConnection(ctx context.Context, link *transport.Link) error { | ||||||
|  | 	outboundMeta := session.OutboundFromContext(ctx) | ||||||
|  | 	if outboundMeta == nil { | ||||||
|  | 		return newError("outbound metadata not found").AtError() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if isDomain(outboundMeta.Target, p.domain) { | ||||||
|  | 		muxClient, err := mux.NewClientWorker(*link, mux.ClientStrategy{}) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return newError("failed to create mux client worker").Base(err).AtWarning() | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		worker, err := NewPortalWorker(muxClient) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return newError("failed to create portal worker").Base(err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		p.picker.AddWorker(worker) | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return p.client.Dispatch(ctx, link) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Outbound struct { | ||||||
|  | 	portal *Portal | ||||||
|  | 	tag    string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (o *Outbound) Tag() string { | ||||||
|  | 	return o.tag | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (o *Outbound) Dispatch(ctx context.Context, link *transport.Link) { | ||||||
|  | 	if err := o.portal.HandleConnection(ctx, link); err != nil { | ||||||
|  | 		newError("failed to process reverse connection").Base(err).WriteToLog(session.ExportIDToError(ctx)) | ||||||
|  | 		common.Interrupt(link.Writer) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (o *Outbound) Start() error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (o *Outbound) Close() error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type StaticMuxPicker struct { | ||||||
|  | 	access  sync.Mutex | ||||||
|  | 	workers []*PortalWorker | ||||||
|  | 	cTask   *task.Periodic | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewStaticMuxPicker() (*StaticMuxPicker, error) { | ||||||
|  | 	p := &StaticMuxPicker{} | ||||||
|  | 	p.cTask = &task.Periodic{ | ||||||
|  | 		Execute:  p.cleanup, | ||||||
|  | 		Interval: time.Second * 30, | ||||||
|  | 	} | ||||||
|  | 	p.cTask.Start() | ||||||
|  | 	return p, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (p *StaticMuxPicker) cleanup() error { | ||||||
|  | 	p.access.Lock() | ||||||
|  | 	defer p.access.Unlock() | ||||||
|  |  | ||||||
|  | 	var activeWorkers []*PortalWorker | ||||||
|  | 	for _, w := range p.workers { | ||||||
|  | 		if !w.Closed() { | ||||||
|  | 			activeWorkers = append(activeWorkers, w) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(activeWorkers) != len(p.workers) { | ||||||
|  | 		p.workers = activeWorkers | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (p *StaticMuxPicker) PickAvailable() (*mux.ClientWorker, error) { | ||||||
|  | 	p.access.Lock() | ||||||
|  | 	defer p.access.Unlock() | ||||||
|  |  | ||||||
|  | 	if len(p.workers) == 0 { | ||||||
|  | 		return nil, newError("empty worker list") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var minIdx int = -1 | ||||||
|  | 	var minConn uint32 = 9999 | ||||||
|  | 	for i, w := range p.workers { | ||||||
|  | 		if w.draining { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		if w.client.ActiveConnections() < minConn { | ||||||
|  | 			minConn = w.client.ActiveConnections() | ||||||
|  | 			minIdx = i | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if minIdx == -1 { | ||||||
|  | 		for i, w := range p.workers { | ||||||
|  | 			if w.IsFull() { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			if w.client.ActiveConnections() < minConn { | ||||||
|  | 				minConn = w.client.ActiveConnections() | ||||||
|  | 				minIdx = i | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if minIdx != -1 { | ||||||
|  | 		return p.workers[minIdx].client, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil, newError("no mux client worker available") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (p *StaticMuxPicker) AddWorker(worker *PortalWorker) { | ||||||
|  | 	p.access.Lock() | ||||||
|  | 	defer p.access.Unlock() | ||||||
|  |  | ||||||
|  | 	p.workers = append(p.workers, worker) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type PortalWorker struct { | ||||||
|  | 	client   *mux.ClientWorker | ||||||
|  | 	control  *task.Periodic | ||||||
|  | 	writer   buf.Writer | ||||||
|  | 	reader   buf.Reader | ||||||
|  | 	draining bool | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewPortalWorker(client *mux.ClientWorker) (*PortalWorker, error) { | ||||||
|  | 	opt := []pipe.Option{pipe.WithSizeLimit(16 * 1024)} | ||||||
|  | 	uplinkReader, uplinkWriter := pipe.New(opt...) | ||||||
|  | 	downlinkReader, downlinkWriter := pipe.New(opt...) | ||||||
|  |  | ||||||
|  | 	ctx := context.Background() | ||||||
|  | 	ctx = session.ContextWithOutbound(ctx, &session.Outbound{ | ||||||
|  | 		Target: net.UDPDestination(net.DomainAddress(internalDomain), 0), | ||||||
|  | 	}) | ||||||
|  | 	f := client.Dispatch(ctx, &transport.Link{ | ||||||
|  | 		Reader: uplinkReader, | ||||||
|  | 		Writer: downlinkWriter, | ||||||
|  | 	}) | ||||||
|  | 	if !f { | ||||||
|  | 		return nil, newError("unable to dispatch control connection") | ||||||
|  | 	} | ||||||
|  | 	w := &PortalWorker{ | ||||||
|  | 		client: client, | ||||||
|  | 		reader: downlinkReader, | ||||||
|  | 		writer: uplinkWriter, | ||||||
|  | 	} | ||||||
|  | 	w.control = &task.Periodic{ | ||||||
|  | 		Execute:  w.heartbeat, | ||||||
|  | 		Interval: time.Second * 2, | ||||||
|  | 	} | ||||||
|  | 	w.control.Start() | ||||||
|  | 	return w, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (w *PortalWorker) heartbeat() error { | ||||||
|  | 	if w.client.Closed() { | ||||||
|  | 		return newError("client worker stopped") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if w.draining || w.writer == nil { | ||||||
|  | 		return newError("already disposed") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	msg := &Control{} | ||||||
|  | 	msg.FillInRandom() | ||||||
|  |  | ||||||
|  | 	if w.client.TotalConnections() > 256 { | ||||||
|  | 		w.draining = true | ||||||
|  | 		msg.State = Control_DRAIN | ||||||
|  |  | ||||||
|  | 		defer func() { | ||||||
|  | 			common.Close(w.writer) | ||||||
|  | 			common.Interrupt(w.reader) | ||||||
|  | 			w.writer = nil | ||||||
|  | 		}() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	b, err := proto.Marshal(msg) | ||||||
|  | 	common.Must(err) | ||||||
|  | 	mb := buf.MergeBytes(nil, b) | ||||||
|  | 	return w.writer.WriteMultiBuffer(mb) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (w *PortalWorker) IsFull() bool { | ||||||
|  | 	return w.client.IsFull() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (w *PortalWorker) Closed() bool { | ||||||
|  | 	return w.client.Closed() | ||||||
|  | } | ||||||
							
								
								
									
										20
									
								
								app/reverse/portal_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								app/reverse/portal_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | |||||||
|  | package reverse_test | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"github.com/xtls/xray-core/v1/app/reverse" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestStaticPickerEmpty(t *testing.T) { | ||||||
|  | 	picker, err := reverse.NewStaticMuxPicker() | ||||||
|  | 	common.Must(err) | ||||||
|  | 	worker, err := picker.PickAvailable() | ||||||
|  | 	if err == nil { | ||||||
|  | 		t.Error("expected error, but nil") | ||||||
|  | 	} | ||||||
|  | 	if worker != nil { | ||||||
|  | 		t.Error("expected nil worker, but not nil") | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										98
									
								
								app/reverse/reverse.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								app/reverse/reverse.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,98 @@ | |||||||
|  | // +build !confonly | ||||||
|  |  | ||||||
|  | package reverse | ||||||
|  |  | ||||||
|  | //go:generate go run github.com/xtls/xray-core/v1/common/errors/errorgen | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  |  | ||||||
|  | 	"github.com/xtls/xray-core/v1/common" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/errors" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/net" | ||||||
|  | 	core "github.com/xtls/xray-core/v1/core" | ||||||
|  | 	"github.com/xtls/xray-core/v1/features/outbound" | ||||||
|  | 	"github.com/xtls/xray-core/v1/features/routing" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	internalDomain = "reverse.internal.example.com" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func isDomain(dest net.Destination, domain string) bool { | ||||||
|  | 	return dest.Address.Family().IsDomain() && dest.Address.Domain() == domain | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func isInternalDomain(dest net.Destination) bool { | ||||||
|  | 	return isDomain(dest, internalDomain) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func init() { | ||||||
|  | 	common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { | ||||||
|  | 		r := new(Reverse) | ||||||
|  | 		if err := core.RequireFeatures(ctx, func(d routing.Dispatcher, om outbound.Manager) error { | ||||||
|  | 			return r.Init(config.(*Config), d, om) | ||||||
|  | 		}); err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		return r, nil | ||||||
|  | 	})) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Reverse struct { | ||||||
|  | 	bridges []*Bridge | ||||||
|  | 	portals []*Portal | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r *Reverse) Init(config *Config, d routing.Dispatcher, ohm outbound.Manager) error { | ||||||
|  | 	for _, bConfig := range config.BridgeConfig { | ||||||
|  | 		b, err := NewBridge(bConfig, d) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		r.bridges = append(r.bridges, b) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, pConfig := range config.PortalConfig { | ||||||
|  | 		p, err := NewPortal(pConfig, ohm) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		r.portals = append(r.portals, p) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r *Reverse) Type() interface{} { | ||||||
|  | 	return (*Reverse)(nil) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r *Reverse) Start() error { | ||||||
|  | 	for _, b := range r.bridges { | ||||||
|  | 		if err := b.Start(); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, p := range r.portals { | ||||||
|  | 		if err := p.Start(); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r *Reverse) Close() error { | ||||||
|  | 	var errs []error | ||||||
|  | 	for _, b := range r.bridges { | ||||||
|  | 		errs = append(errs, b.Close()) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, p := range r.portals { | ||||||
|  | 		errs = append(errs, p.Close()) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return errors.Combine(errs...) | ||||||
|  | } | ||||||
							
								
								
									
										46
									
								
								app/router/balancing.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								app/router/balancing.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | |||||||
|  | // +build !confonly | ||||||
|  |  | ||||||
|  | package router | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/dice" | ||||||
|  | 	"github.com/xtls/xray-core/v1/features/outbound" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type BalancingStrategy interface { | ||||||
|  | 	PickOutbound([]string) string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type RandomStrategy struct { | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *RandomStrategy) PickOutbound(tags []string) string { | ||||||
|  | 	n := len(tags) | ||||||
|  | 	if n == 0 { | ||||||
|  | 		panic("0 tags") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return tags[dice.Roll(n)] | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Balancer struct { | ||||||
|  | 	selectors []string | ||||||
|  | 	strategy  BalancingStrategy | ||||||
|  | 	ohm       outbound.Manager | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Balancer) PickOutbound() (string, error) { | ||||||
|  | 	hs, ok := b.ohm.(outbound.HandlerSelector) | ||||||
|  | 	if !ok { | ||||||
|  | 		return "", newError("outbound.Manager is not a HandlerSelector") | ||||||
|  | 	} | ||||||
|  | 	tags := hs.Select(b.selectors) | ||||||
|  | 	if len(tags) == 0 { | ||||||
|  | 		return "", newError("no available outbounds selected") | ||||||
|  | 	} | ||||||
|  | 	tag := b.strategy.PickOutbound(tags) | ||||||
|  | 	if tag == "" { | ||||||
|  | 		return "", newError("balancing strategy returns empty tag") | ||||||
|  | 	} | ||||||
|  | 	return tag, nil | ||||||
|  | } | ||||||
							
								
								
									
										95
									
								
								app/router/command/command.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								app/router/command/command.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,95 @@ | |||||||
|  | // +build !confonly | ||||||
|  |  | ||||||
|  | package command | ||||||
|  |  | ||||||
|  | //go:generate go run github.com/xtls/xray-core/v1/common/errors/errorgen | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"google.golang.org/grpc" | ||||||
|  |  | ||||||
|  | 	"github.com/xtls/xray-core/v1/common" | ||||||
|  | 	"github.com/xtls/xray-core/v1/core" | ||||||
|  | 	"github.com/xtls/xray-core/v1/features/routing" | ||||||
|  | 	"github.com/xtls/xray-core/v1/features/stats" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // routingServer is an implementation of RoutingService. | ||||||
|  | type routingServer struct { | ||||||
|  | 	router       routing.Router | ||||||
|  | 	routingStats stats.Channel | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // NewRoutingServer creates a statistics service with statistics manager. | ||||||
|  | func NewRoutingServer(router routing.Router, routingStats stats.Channel) RoutingServiceServer { | ||||||
|  | 	return &routingServer{ | ||||||
|  | 		router:       router, | ||||||
|  | 		routingStats: routingStats, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *routingServer) TestRoute(ctx context.Context, request *TestRouteRequest) (*RoutingContext, error) { | ||||||
|  | 	if request.RoutingContext == nil { | ||||||
|  | 		return nil, newError("Invalid routing request.") | ||||||
|  | 	} | ||||||
|  | 	route, err := s.router.PickRoute(AsRoutingContext(request.RoutingContext)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	if request.PublishResult && s.routingStats != nil { | ||||||
|  | 		ctx, _ := context.WithTimeout(context.Background(), 4*time.Second) | ||||||
|  | 		s.routingStats.Publish(ctx, route) | ||||||
|  | 	} | ||||||
|  | 	return AsProtobufMessage(request.FieldSelectors)(route), nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *routingServer) SubscribeRoutingStats(request *SubscribeRoutingStatsRequest, stream RoutingService_SubscribeRoutingStatsServer) error { | ||||||
|  | 	if s.routingStats == nil { | ||||||
|  | 		return newError("Routing statistics not enabled.") | ||||||
|  | 	} | ||||||
|  | 	genMessage := AsProtobufMessage(request.FieldSelectors) | ||||||
|  | 	subscriber, err := stats.SubscribeRunnableChannel(s.routingStats) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	defer stats.UnsubscribeClosableChannel(s.routingStats, subscriber) | ||||||
|  | 	for { | ||||||
|  | 		select { | ||||||
|  | 		case value, ok := <-subscriber: | ||||||
|  | 			if !ok { | ||||||
|  | 				return newError("Upstream closed the subscriber channel.") | ||||||
|  | 			} | ||||||
|  | 			route, ok := value.(routing.Route) | ||||||
|  | 			if !ok { | ||||||
|  | 				return newError("Upstream sent malformed statistics.") | ||||||
|  | 			} | ||||||
|  | 			err := stream.Send(genMessage(route)) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 		case <-stream.Context().Done(): | ||||||
|  | 			return stream.Context().Err() | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *routingServer) mustEmbedUnimplementedRoutingServiceServer() {} | ||||||
|  |  | ||||||
|  | type service struct { | ||||||
|  | 	v *core.Instance | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *service) Register(server *grpc.Server) { | ||||||
|  | 	common.Must(s.v.RequireFeatures(func(router routing.Router, stats stats.Manager) { | ||||||
|  | 		RegisterRoutingServiceServer(server, NewRoutingServer(router, nil)) | ||||||
|  | 	})) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func init() { | ||||||
|  | 	common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, cfg interface{}) (interface{}, error) { | ||||||
|  | 		s := core.MustFromContext(ctx) | ||||||
|  | 		return &service{v: s}, nil | ||||||
|  | 	})) | ||||||
|  | } | ||||||
							
								
								
									
										532
									
								
								app/router/command/command.pb.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										532
									
								
								app/router/command/command.pb.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,532 @@ | |||||||
|  | // Code generated by protoc-gen-go. DO NOT EDIT. | ||||||
|  | // versions: | ||||||
|  | // 	protoc-gen-go v1.25.0 | ||||||
|  | // 	protoc        v3.14.0 | ||||||
|  | // source: app/router/command/command.proto | ||||||
|  |  | ||||||
|  | package command | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	proto "github.com/golang/protobuf/proto" | ||||||
|  | 	net "github.com/xtls/xray-core/v1/common/net" | ||||||
|  | 	protoreflect "google.golang.org/protobuf/reflect/protoreflect" | ||||||
|  | 	protoimpl "google.golang.org/protobuf/runtime/protoimpl" | ||||||
|  | 	reflect "reflect" | ||||||
|  | 	sync "sync" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	// Verify that this generated code is sufficiently up-to-date. | ||||||
|  | 	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) | ||||||
|  | 	// Verify that runtime/protoimpl is sufficiently up-to-date. | ||||||
|  | 	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // This is a compile-time assertion that a sufficiently up-to-date version | ||||||
|  | // of the legacy proto package is being used. | ||||||
|  | const _ = proto.ProtoPackageIsVersion4 | ||||||
|  |  | ||||||
|  | // RoutingContext is the context with information relative to routing process. | ||||||
|  | // It conforms to the structure of xray.features.routing.Context and | ||||||
|  | // xray.features.routing.Route. | ||||||
|  | type RoutingContext struct { | ||||||
|  | 	state         protoimpl.MessageState | ||||||
|  | 	sizeCache     protoimpl.SizeCache | ||||||
|  | 	unknownFields protoimpl.UnknownFields | ||||||
|  |  | ||||||
|  | 	InboundTag        string            `protobuf:"bytes,1,opt,name=InboundTag,proto3" json:"InboundTag,omitempty"` | ||||||
|  | 	Network           net.Network       `protobuf:"varint,2,opt,name=Network,proto3,enum=xray.common.net.Network" json:"Network,omitempty"` | ||||||
|  | 	SourceIPs         [][]byte          `protobuf:"bytes,3,rep,name=SourceIPs,proto3" json:"SourceIPs,omitempty"` | ||||||
|  | 	TargetIPs         [][]byte          `protobuf:"bytes,4,rep,name=TargetIPs,proto3" json:"TargetIPs,omitempty"` | ||||||
|  | 	SourcePort        uint32            `protobuf:"varint,5,opt,name=SourcePort,proto3" json:"SourcePort,omitempty"` | ||||||
|  | 	TargetPort        uint32            `protobuf:"varint,6,opt,name=TargetPort,proto3" json:"TargetPort,omitempty"` | ||||||
|  | 	TargetDomain      string            `protobuf:"bytes,7,opt,name=TargetDomain,proto3" json:"TargetDomain,omitempty"` | ||||||
|  | 	Protocol          string            `protobuf:"bytes,8,opt,name=Protocol,proto3" json:"Protocol,omitempty"` | ||||||
|  | 	User              string            `protobuf:"bytes,9,opt,name=User,proto3" json:"User,omitempty"` | ||||||
|  | 	Attributes        map[string]string `protobuf:"bytes,10,rep,name=Attributes,proto3" json:"Attributes,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` | ||||||
|  | 	OutboundGroupTags []string          `protobuf:"bytes,11,rep,name=OutboundGroupTags,proto3" json:"OutboundGroupTags,omitempty"` | ||||||
|  | 	OutboundTag       string            `protobuf:"bytes,12,opt,name=OutboundTag,proto3" json:"OutboundTag,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *RoutingContext) Reset() { | ||||||
|  | 	*x = RoutingContext{} | ||||||
|  | 	if protoimpl.UnsafeEnabled { | ||||||
|  | 		mi := &file_app_router_command_command_proto_msgTypes[0] | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		ms.StoreMessageInfo(mi) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *RoutingContext) String() string { | ||||||
|  | 	return protoimpl.X.MessageStringOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (*RoutingContext) ProtoMessage() {} | ||||||
|  |  | ||||||
|  | func (x *RoutingContext) ProtoReflect() protoreflect.Message { | ||||||
|  | 	mi := &file_app_router_command_command_proto_msgTypes[0] | ||||||
|  | 	if protoimpl.UnsafeEnabled && x != nil { | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		if ms.LoadMessageInfo() == nil { | ||||||
|  | 			ms.StoreMessageInfo(mi) | ||||||
|  | 		} | ||||||
|  | 		return ms | ||||||
|  | 	} | ||||||
|  | 	return mi.MessageOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Deprecated: Use RoutingContext.ProtoReflect.Descriptor instead. | ||||||
|  | func (*RoutingContext) Descriptor() ([]byte, []int) { | ||||||
|  | 	return file_app_router_command_command_proto_rawDescGZIP(), []int{0} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *RoutingContext) GetInboundTag() string { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.InboundTag | ||||||
|  | 	} | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *RoutingContext) GetNetwork() net.Network { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Network | ||||||
|  | 	} | ||||||
|  | 	return net.Network_Unknown | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *RoutingContext) GetSourceIPs() [][]byte { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.SourceIPs | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *RoutingContext) GetTargetIPs() [][]byte { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.TargetIPs | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *RoutingContext) GetSourcePort() uint32 { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.SourcePort | ||||||
|  | 	} | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *RoutingContext) GetTargetPort() uint32 { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.TargetPort | ||||||
|  | 	} | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *RoutingContext) GetTargetDomain() string { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.TargetDomain | ||||||
|  | 	} | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *RoutingContext) GetProtocol() string { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Protocol | ||||||
|  | 	} | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *RoutingContext) GetUser() string { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.User | ||||||
|  | 	} | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *RoutingContext) GetAttributes() map[string]string { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Attributes | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *RoutingContext) GetOutboundGroupTags() []string { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.OutboundGroupTags | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *RoutingContext) GetOutboundTag() string { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.OutboundTag | ||||||
|  | 	} | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // SubscribeRoutingStatsRequest subscribes to routing statistics channel if | ||||||
|  | // opened by xray-core. | ||||||
|  | // * FieldSelectors selects a subset of fields in routing statistics to return. | ||||||
|  | // Valid selectors: | ||||||
|  | //  - inbound: Selects connection's inbound tag. | ||||||
|  | //  - network: Selects connection's network. | ||||||
|  | //  - ip: Equivalent as "ip_source" and "ip_target", selects both source and | ||||||
|  | //  target IP. | ||||||
|  | //  - port: Equivalent as "port_source" and "port_target", selects both source | ||||||
|  | //  and target port. | ||||||
|  | //  - domain: Selects target domain. | ||||||
|  | //  - protocol: Select connection's protocol. | ||||||
|  | //  - user: Select connection's inbound user email. | ||||||
|  | //  - attributes: Select connection's additional attributes. | ||||||
|  | //  - outbound: Equivalent as "outbound" and "outbound_group", select both | ||||||
|  | //  outbound tag and outbound group tags. | ||||||
|  | // * If FieldSelectors is left empty, all fields will be returned. | ||||||
|  | type SubscribeRoutingStatsRequest struct { | ||||||
|  | 	state         protoimpl.MessageState | ||||||
|  | 	sizeCache     protoimpl.SizeCache | ||||||
|  | 	unknownFields protoimpl.UnknownFields | ||||||
|  |  | ||||||
|  | 	FieldSelectors []string `protobuf:"bytes,1,rep,name=FieldSelectors,proto3" json:"FieldSelectors,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *SubscribeRoutingStatsRequest) Reset() { | ||||||
|  | 	*x = SubscribeRoutingStatsRequest{} | ||||||
|  | 	if protoimpl.UnsafeEnabled { | ||||||
|  | 		mi := &file_app_router_command_command_proto_msgTypes[1] | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		ms.StoreMessageInfo(mi) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *SubscribeRoutingStatsRequest) String() string { | ||||||
|  | 	return protoimpl.X.MessageStringOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (*SubscribeRoutingStatsRequest) ProtoMessage() {} | ||||||
|  |  | ||||||
|  | func (x *SubscribeRoutingStatsRequest) ProtoReflect() protoreflect.Message { | ||||||
|  | 	mi := &file_app_router_command_command_proto_msgTypes[1] | ||||||
|  | 	if protoimpl.UnsafeEnabled && x != nil { | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		if ms.LoadMessageInfo() == nil { | ||||||
|  | 			ms.StoreMessageInfo(mi) | ||||||
|  | 		} | ||||||
|  | 		return ms | ||||||
|  | 	} | ||||||
|  | 	return mi.MessageOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Deprecated: Use SubscribeRoutingStatsRequest.ProtoReflect.Descriptor instead. | ||||||
|  | func (*SubscribeRoutingStatsRequest) Descriptor() ([]byte, []int) { | ||||||
|  | 	return file_app_router_command_command_proto_rawDescGZIP(), []int{1} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *SubscribeRoutingStatsRequest) GetFieldSelectors() []string { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.FieldSelectors | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // TestRouteRequest manually tests a routing result according to the routing | ||||||
|  | // context message. | ||||||
|  | // * RoutingContext is the routing message without outbound information. | ||||||
|  | // * FieldSelectors selects the fields to return in the routing result. All | ||||||
|  | // fields are returned if left empty. | ||||||
|  | // * PublishResult broadcasts the routing result to routing statistics channel | ||||||
|  | // if set true. | ||||||
|  | type TestRouteRequest struct { | ||||||
|  | 	state         protoimpl.MessageState | ||||||
|  | 	sizeCache     protoimpl.SizeCache | ||||||
|  | 	unknownFields protoimpl.UnknownFields | ||||||
|  |  | ||||||
|  | 	RoutingContext *RoutingContext `protobuf:"bytes,1,opt,name=RoutingContext,proto3" json:"RoutingContext,omitempty"` | ||||||
|  | 	FieldSelectors []string        `protobuf:"bytes,2,rep,name=FieldSelectors,proto3" json:"FieldSelectors,omitempty"` | ||||||
|  | 	PublishResult  bool            `protobuf:"varint,3,opt,name=PublishResult,proto3" json:"PublishResult,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *TestRouteRequest) Reset() { | ||||||
|  | 	*x = TestRouteRequest{} | ||||||
|  | 	if protoimpl.UnsafeEnabled { | ||||||
|  | 		mi := &file_app_router_command_command_proto_msgTypes[2] | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		ms.StoreMessageInfo(mi) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *TestRouteRequest) String() string { | ||||||
|  | 	return protoimpl.X.MessageStringOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (*TestRouteRequest) ProtoMessage() {} | ||||||
|  |  | ||||||
|  | func (x *TestRouteRequest) ProtoReflect() protoreflect.Message { | ||||||
|  | 	mi := &file_app_router_command_command_proto_msgTypes[2] | ||||||
|  | 	if protoimpl.UnsafeEnabled && x != nil { | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		if ms.LoadMessageInfo() == nil { | ||||||
|  | 			ms.StoreMessageInfo(mi) | ||||||
|  | 		} | ||||||
|  | 		return ms | ||||||
|  | 	} | ||||||
|  | 	return mi.MessageOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Deprecated: Use TestRouteRequest.ProtoReflect.Descriptor instead. | ||||||
|  | func (*TestRouteRequest) Descriptor() ([]byte, []int) { | ||||||
|  | 	return file_app_router_command_command_proto_rawDescGZIP(), []int{2} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *TestRouteRequest) GetRoutingContext() *RoutingContext { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.RoutingContext | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *TestRouteRequest) GetFieldSelectors() []string { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.FieldSelectors | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *TestRouteRequest) GetPublishResult() bool { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.PublishResult | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Config struct { | ||||||
|  | 	state         protoimpl.MessageState | ||||||
|  | 	sizeCache     protoimpl.SizeCache | ||||||
|  | 	unknownFields protoimpl.UnknownFields | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Config) Reset() { | ||||||
|  | 	*x = Config{} | ||||||
|  | 	if protoimpl.UnsafeEnabled { | ||||||
|  | 		mi := &file_app_router_command_command_proto_msgTypes[3] | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		ms.StoreMessageInfo(mi) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Config) String() string { | ||||||
|  | 	return protoimpl.X.MessageStringOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (*Config) ProtoMessage() {} | ||||||
|  |  | ||||||
|  | func (x *Config) ProtoReflect() protoreflect.Message { | ||||||
|  | 	mi := &file_app_router_command_command_proto_msgTypes[3] | ||||||
|  | 	if protoimpl.UnsafeEnabled && x != nil { | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		if ms.LoadMessageInfo() == nil { | ||||||
|  | 			ms.StoreMessageInfo(mi) | ||||||
|  | 		} | ||||||
|  | 		return ms | ||||||
|  | 	} | ||||||
|  | 	return mi.MessageOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Deprecated: Use Config.ProtoReflect.Descriptor instead. | ||||||
|  | func (*Config) Descriptor() ([]byte, []int) { | ||||||
|  | 	return file_app_router_command_command_proto_rawDescGZIP(), []int{3} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var File_app_router_command_command_proto protoreflect.FileDescriptor | ||||||
|  |  | ||||||
|  | var file_app_router_command_command_proto_rawDesc = []byte{ | ||||||
|  | 	0x0a, 0x20, 0x61, 0x70, 0x70, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x6d, | ||||||
|  | 	0x6d, 0x61, 0x6e, 0x64, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x70, 0x72, 0x6f, | ||||||
|  | 	0x74, 0x6f, 0x12, 0x17, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, | ||||||
|  | 	0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x1a, 0x18, 0x63, 0x6f, 0x6d, | ||||||
|  | 	0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2e, | ||||||
|  | 	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x9c, 0x04, 0x0a, 0x0e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, | ||||||
|  | 	0x67, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x49, 0x6e, 0x62, 0x6f, | ||||||
|  | 	0x75, 0x6e, 0x64, 0x54, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x49, 0x6e, | ||||||
|  | 	0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x61, 0x67, 0x12, 0x32, 0x0a, 0x07, 0x4e, 0x65, 0x74, 0x77, | ||||||
|  | 	0x6f, 0x72, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x78, 0x72, 0x61, 0x79, | ||||||
|  | 	0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x4e, 0x65, 0x74, 0x77, | ||||||
|  | 	0x6f, 0x72, 0x6b, 0x52, 0x07, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x1c, 0x0a, 0x09, | ||||||
|  | 	0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x50, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, | ||||||
|  | 	0x09, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x50, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x54, 0x61, | ||||||
|  | 	0x72, 0x67, 0x65, 0x74, 0x49, 0x50, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x09, 0x54, | ||||||
|  | 	0x61, 0x72, 0x67, 0x65, 0x74, 0x49, 0x50, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x53, 0x6f, 0x75, 0x72, | ||||||
|  | 	0x63, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x53, 0x6f, | ||||||
|  | 	0x75, 0x72, 0x63, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x54, 0x61, 0x72, 0x67, | ||||||
|  | 	0x65, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x54, 0x61, | ||||||
|  | 	0x72, 0x67, 0x65, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x22, 0x0a, 0x0c, 0x54, 0x61, 0x72, 0x67, | ||||||
|  | 	0x65, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, | ||||||
|  | 	0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x1a, 0x0a, 0x08, | ||||||
|  | 	0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, | ||||||
|  | 	0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x55, 0x73, 0x65, 0x72, | ||||||
|  | 	0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x55, 0x73, 0x65, 0x72, 0x12, 0x57, 0x0a, 0x0a, | ||||||
|  | 	0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, | ||||||
|  | 	0x32, 0x37, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, | ||||||
|  | 	0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x69, | ||||||
|  | 	0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x2e, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, | ||||||
|  | 	0x75, 0x74, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x41, 0x74, 0x74, 0x72, 0x69, | ||||||
|  | 	0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x2c, 0x0a, 0x11, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, | ||||||
|  | 	0x64, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x54, 0x61, 0x67, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x09, | ||||||
|  | 	0x52, 0x11, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x54, | ||||||
|  | 	0x61, 0x67, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, | ||||||
|  | 	0x61, 0x67, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, | ||||||
|  | 	0x6e, 0x64, 0x54, 0x61, 0x67, 0x1a, 0x3d, 0x0a, 0x0f, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, | ||||||
|  | 	0x74, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, | ||||||
|  | 	0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, | ||||||
|  | 	0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, | ||||||
|  | 	0x3a, 0x02, 0x38, 0x01, 0x22, 0x46, 0x0a, 0x1c, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, | ||||||
|  | 	0x65, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, | ||||||
|  | 	0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x53, 0x65, 0x6c, | ||||||
|  | 	0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x46, 0x69, | ||||||
|  | 	0x65, 0x6c, 0x64, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x22, 0xb1, 0x01, 0x0a, | ||||||
|  | 	0x10, 0x54, 0x65, 0x73, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, | ||||||
|  | 	0x74, 0x12, 0x4f, 0x0a, 0x0e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x74, | ||||||
|  | 	0x65, 0x78, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x78, 0x72, 0x61, 0x79, | ||||||
|  | 	0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, | ||||||
|  | 	0x61, 0x6e, 0x64, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x74, 0x65, | ||||||
|  | 	0x78, 0x74, 0x52, 0x0e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x74, 0x65, | ||||||
|  | 	0x78, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x53, 0x65, 0x6c, 0x65, 0x63, | ||||||
|  | 	0x74, 0x6f, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x46, 0x69, 0x65, 0x6c, | ||||||
|  | 	0x64, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x50, 0x75, | ||||||
|  | 	0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, | ||||||
|  | 	0x08, 0x52, 0x0d, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, | ||||||
|  | 	0x22, 0x08, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x32, 0xf0, 0x01, 0x0a, 0x0e, 0x52, | ||||||
|  | 	0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x7b, 0x0a, | ||||||
|  | 	0x15, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, | ||||||
|  | 	0x67, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x35, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, | ||||||
|  | 	0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, | ||||||
|  | 	0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, | ||||||
|  | 	0x67, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, | ||||||
|  | 	0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, | ||||||
|  | 	0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x43, | ||||||
|  | 	0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x22, 0x00, 0x30, 0x01, 0x12, 0x61, 0x0a, 0x09, 0x54, 0x65, | ||||||
|  | 	0x73, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x29, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, | ||||||
|  | 	0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, | ||||||
|  | 	0x64, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, | ||||||
|  | 	0x73, 0x74, 0x1a, 0x27, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, | ||||||
|  | 	0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x52, 0x6f, 0x75, | ||||||
|  | 	0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x22, 0x00, 0x42, 0x6a, 0x0a, | ||||||
|  | 	0x1b, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, | ||||||
|  | 	0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x50, 0x01, 0x5a, 0x2f, | ||||||
|  | 	0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, | ||||||
|  | 	0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x70, 0x70, | ||||||
|  | 	0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0xaa, | ||||||
|  | 	0x02, 0x17, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, | ||||||
|  | 	0x72, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, | ||||||
|  | 	0x33, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var ( | ||||||
|  | 	file_app_router_command_command_proto_rawDescOnce sync.Once | ||||||
|  | 	file_app_router_command_command_proto_rawDescData = file_app_router_command_command_proto_rawDesc | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func file_app_router_command_command_proto_rawDescGZIP() []byte { | ||||||
|  | 	file_app_router_command_command_proto_rawDescOnce.Do(func() { | ||||||
|  | 		file_app_router_command_command_proto_rawDescData = protoimpl.X.CompressGZIP(file_app_router_command_command_proto_rawDescData) | ||||||
|  | 	}) | ||||||
|  | 	return file_app_router_command_command_proto_rawDescData | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var file_app_router_command_command_proto_msgTypes = make([]protoimpl.MessageInfo, 5) | ||||||
|  | var file_app_router_command_command_proto_goTypes = []interface{}{ | ||||||
|  | 	(*RoutingContext)(nil),               // 0: xray.app.router.command.RoutingContext | ||||||
|  | 	(*SubscribeRoutingStatsRequest)(nil), // 1: xray.app.router.command.SubscribeRoutingStatsRequest | ||||||
|  | 	(*TestRouteRequest)(nil),             // 2: xray.app.router.command.TestRouteRequest | ||||||
|  | 	(*Config)(nil),                       // 3: xray.app.router.command.Config | ||||||
|  | 	nil,                                  // 4: xray.app.router.command.RoutingContext.AttributesEntry | ||||||
|  | 	(net.Network)(0),                     // 5: xray.common.net.Network | ||||||
|  | } | ||||||
|  | var file_app_router_command_command_proto_depIdxs = []int32{ | ||||||
|  | 	5, // 0: xray.app.router.command.RoutingContext.Network:type_name -> xray.common.net.Network | ||||||
|  | 	4, // 1: xray.app.router.command.RoutingContext.Attributes:type_name -> xray.app.router.command.RoutingContext.AttributesEntry | ||||||
|  | 	0, // 2: xray.app.router.command.TestRouteRequest.RoutingContext:type_name -> xray.app.router.command.RoutingContext | ||||||
|  | 	1, // 3: xray.app.router.command.RoutingService.SubscribeRoutingStats:input_type -> xray.app.router.command.SubscribeRoutingStatsRequest | ||||||
|  | 	2, // 4: xray.app.router.command.RoutingService.TestRoute:input_type -> xray.app.router.command.TestRouteRequest | ||||||
|  | 	0, // 5: xray.app.router.command.RoutingService.SubscribeRoutingStats:output_type -> xray.app.router.command.RoutingContext | ||||||
|  | 	0, // 6: xray.app.router.command.RoutingService.TestRoute:output_type -> xray.app.router.command.RoutingContext | ||||||
|  | 	5, // [5:7] is the sub-list for method output_type | ||||||
|  | 	3, // [3:5] is the sub-list for method input_type | ||||||
|  | 	3, // [3:3] is the sub-list for extension type_name | ||||||
|  | 	3, // [3:3] is the sub-list for extension extendee | ||||||
|  | 	0, // [0:3] is the sub-list for field type_name | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func init() { file_app_router_command_command_proto_init() } | ||||||
|  | func file_app_router_command_command_proto_init() { | ||||||
|  | 	if File_app_router_command_command_proto != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if !protoimpl.UnsafeEnabled { | ||||||
|  | 		file_app_router_command_command_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { | ||||||
|  | 			switch v := v.(*RoutingContext); i { | ||||||
|  | 			case 0: | ||||||
|  | 				return &v.state | ||||||
|  | 			case 1: | ||||||
|  | 				return &v.sizeCache | ||||||
|  | 			case 2: | ||||||
|  | 				return &v.unknownFields | ||||||
|  | 			default: | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		file_app_router_command_command_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { | ||||||
|  | 			switch v := v.(*SubscribeRoutingStatsRequest); i { | ||||||
|  | 			case 0: | ||||||
|  | 				return &v.state | ||||||
|  | 			case 1: | ||||||
|  | 				return &v.sizeCache | ||||||
|  | 			case 2: | ||||||
|  | 				return &v.unknownFields | ||||||
|  | 			default: | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		file_app_router_command_command_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { | ||||||
|  | 			switch v := v.(*TestRouteRequest); i { | ||||||
|  | 			case 0: | ||||||
|  | 				return &v.state | ||||||
|  | 			case 1: | ||||||
|  | 				return &v.sizeCache | ||||||
|  | 			case 2: | ||||||
|  | 				return &v.unknownFields | ||||||
|  | 			default: | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		file_app_router_command_command_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { | ||||||
|  | 			switch v := v.(*Config); i { | ||||||
|  | 			case 0: | ||||||
|  | 				return &v.state | ||||||
|  | 			case 1: | ||||||
|  | 				return &v.sizeCache | ||||||
|  | 			case 2: | ||||||
|  | 				return &v.unknownFields | ||||||
|  | 			default: | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	type x struct{} | ||||||
|  | 	out := protoimpl.TypeBuilder{ | ||||||
|  | 		File: protoimpl.DescBuilder{ | ||||||
|  | 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(), | ||||||
|  | 			RawDescriptor: file_app_router_command_command_proto_rawDesc, | ||||||
|  | 			NumEnums:      0, | ||||||
|  | 			NumMessages:   5, | ||||||
|  | 			NumExtensions: 0, | ||||||
|  | 			NumServices:   1, | ||||||
|  | 		}, | ||||||
|  | 		GoTypes:           file_app_router_command_command_proto_goTypes, | ||||||
|  | 		DependencyIndexes: file_app_router_command_command_proto_depIdxs, | ||||||
|  | 		MessageInfos:      file_app_router_command_command_proto_msgTypes, | ||||||
|  | 	}.Build() | ||||||
|  | 	File_app_router_command_command_proto = out.File | ||||||
|  | 	file_app_router_command_command_proto_rawDesc = nil | ||||||
|  | 	file_app_router_command_command_proto_goTypes = nil | ||||||
|  | 	file_app_router_command_command_proto_depIdxs = nil | ||||||
|  | } | ||||||
							
								
								
									
										69
									
								
								app/router/command/command.proto
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								app/router/command/command.proto
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | |||||||
|  | syntax = "proto3"; | ||||||
|  |  | ||||||
|  | package xray.app.router.command; | ||||||
|  | option csharp_namespace = "Xray.App.Router.Command"; | ||||||
|  | option go_package = "github.com/xtls/xray-core/v1/app/router/command"; | ||||||
|  | option java_package = "com.xray.app.router.command"; | ||||||
|  | option java_multiple_files = true; | ||||||
|  |  | ||||||
|  | import "common/net/network.proto"; | ||||||
|  |  | ||||||
|  | // RoutingContext is the context with information relative to routing process. | ||||||
|  | // It conforms to the structure of xray.features.routing.Context and | ||||||
|  | // xray.features.routing.Route. | ||||||
|  | message RoutingContext { | ||||||
|  |   string InboundTag = 1; | ||||||
|  |   xray.common.net.Network Network = 2; | ||||||
|  |   repeated bytes SourceIPs = 3; | ||||||
|  |   repeated bytes TargetIPs = 4; | ||||||
|  |   uint32 SourcePort = 5; | ||||||
|  |   uint32 TargetPort = 6; | ||||||
|  |   string TargetDomain = 7; | ||||||
|  |   string Protocol = 8; | ||||||
|  |   string User = 9; | ||||||
|  |   map<string, string> Attributes = 10; | ||||||
|  |   repeated string OutboundGroupTags = 11; | ||||||
|  |   string OutboundTag = 12; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // SubscribeRoutingStatsRequest subscribes to routing statistics channel if | ||||||
|  | // opened by xray-core. | ||||||
|  | // * FieldSelectors selects a subset of fields in routing statistics to return. | ||||||
|  | // Valid selectors: | ||||||
|  | //  - inbound: Selects connection's inbound tag. | ||||||
|  | //  - network: Selects connection's network. | ||||||
|  | //  - ip: Equivalent as "ip_source" and "ip_target", selects both source and | ||||||
|  | //  target IP. | ||||||
|  | //  - port: Equivalent as "port_source" and "port_target", selects both source | ||||||
|  | //  and target port. | ||||||
|  | //  - domain: Selects target domain. | ||||||
|  | //  - protocol: Select connection's protocol. | ||||||
|  | //  - user: Select connection's inbound user email. | ||||||
|  | //  - attributes: Select connection's additional attributes. | ||||||
|  | //  - outbound: Equivalent as "outbound" and "outbound_group", select both | ||||||
|  | //  outbound tag and outbound group tags. | ||||||
|  | // * If FieldSelectors is left empty, all fields will be returned. | ||||||
|  | message SubscribeRoutingStatsRequest { | ||||||
|  |   repeated string FieldSelectors = 1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // TestRouteRequest manually tests a routing result according to the routing | ||||||
|  | // context message. | ||||||
|  | // * RoutingContext is the routing message without outbound information. | ||||||
|  | // * FieldSelectors selects the fields to return in the routing result. All | ||||||
|  | // fields are returned if left empty. | ||||||
|  | // * PublishResult broadcasts the routing result to routing statistics channel | ||||||
|  | // if set true. | ||||||
|  | message TestRouteRequest { | ||||||
|  |   RoutingContext RoutingContext = 1; | ||||||
|  |   repeated string FieldSelectors = 2; | ||||||
|  |   bool PublishResult = 3; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | service RoutingService { | ||||||
|  |   rpc SubscribeRoutingStats(SubscribeRoutingStatsRequest) | ||||||
|  |       returns (stream RoutingContext) {} | ||||||
|  |   rpc TestRoute(TestRouteRequest) returns (RoutingContext) {} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message Config {} | ||||||
							
								
								
									
										161
									
								
								app/router/command/command_grpc.pb.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										161
									
								
								app/router/command/command_grpc.pb.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,161 @@ | |||||||
|  | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. | ||||||
|  |  | ||||||
|  | package command | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	context "context" | ||||||
|  | 	grpc "google.golang.org/grpc" | ||||||
|  | 	codes "google.golang.org/grpc/codes" | ||||||
|  | 	status "google.golang.org/grpc/status" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // This is a compile-time assertion to ensure that this generated file | ||||||
|  | // is compatible with the grpc package it is being compiled against. | ||||||
|  | const _ = grpc.SupportPackageIsVersion7 | ||||||
|  |  | ||||||
|  | // RoutingServiceClient is the client API for RoutingService service. | ||||||
|  | // | ||||||
|  | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. | ||||||
|  | type RoutingServiceClient interface { | ||||||
|  | 	SubscribeRoutingStats(ctx context.Context, in *SubscribeRoutingStatsRequest, opts ...grpc.CallOption) (RoutingService_SubscribeRoutingStatsClient, error) | ||||||
|  | 	TestRoute(ctx context.Context, in *TestRouteRequest, opts ...grpc.CallOption) (*RoutingContext, error) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type routingServiceClient struct { | ||||||
|  | 	cc grpc.ClientConnInterface | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewRoutingServiceClient(cc grpc.ClientConnInterface) RoutingServiceClient { | ||||||
|  | 	return &routingServiceClient{cc} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *routingServiceClient) SubscribeRoutingStats(ctx context.Context, in *SubscribeRoutingStatsRequest, opts ...grpc.CallOption) (RoutingService_SubscribeRoutingStatsClient, error) { | ||||||
|  | 	stream, err := c.cc.NewStream(ctx, &_RoutingService_serviceDesc.Streams[0], "/xray.app.router.command.RoutingService/SubscribeRoutingStats", opts...) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	x := &routingServiceSubscribeRoutingStatsClient{stream} | ||||||
|  | 	if err := x.ClientStream.SendMsg(in); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	if err := x.ClientStream.CloseSend(); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return x, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type RoutingService_SubscribeRoutingStatsClient interface { | ||||||
|  | 	Recv() (*RoutingContext, error) | ||||||
|  | 	grpc.ClientStream | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type routingServiceSubscribeRoutingStatsClient struct { | ||||||
|  | 	grpc.ClientStream | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *routingServiceSubscribeRoutingStatsClient) Recv() (*RoutingContext, error) { | ||||||
|  | 	m := new(RoutingContext) | ||||||
|  | 	if err := x.ClientStream.RecvMsg(m); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return m, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *routingServiceClient) TestRoute(ctx context.Context, in *TestRouteRequest, opts ...grpc.CallOption) (*RoutingContext, error) { | ||||||
|  | 	out := new(RoutingContext) | ||||||
|  | 	err := c.cc.Invoke(ctx, "/xray.app.router.command.RoutingService/TestRoute", in, out, opts...) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return out, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // RoutingServiceServer is the server API for RoutingService service. | ||||||
|  | // All implementations must embed UnimplementedRoutingServiceServer | ||||||
|  | // for forward compatibility | ||||||
|  | type RoutingServiceServer interface { | ||||||
|  | 	SubscribeRoutingStats(*SubscribeRoutingStatsRequest, RoutingService_SubscribeRoutingStatsServer) error | ||||||
|  | 	TestRoute(context.Context, *TestRouteRequest) (*RoutingContext, error) | ||||||
|  | 	mustEmbedUnimplementedRoutingServiceServer() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // UnimplementedRoutingServiceServer must be embedded to have forward compatible implementations. | ||||||
|  | type UnimplementedRoutingServiceServer struct { | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (UnimplementedRoutingServiceServer) SubscribeRoutingStats(*SubscribeRoutingStatsRequest, RoutingService_SubscribeRoutingStatsServer) error { | ||||||
|  | 	return status.Errorf(codes.Unimplemented, "method SubscribeRoutingStats not implemented") | ||||||
|  | } | ||||||
|  | func (UnimplementedRoutingServiceServer) TestRoute(context.Context, *TestRouteRequest) (*RoutingContext, error) { | ||||||
|  | 	return nil, status.Errorf(codes.Unimplemented, "method TestRoute not implemented") | ||||||
|  | } | ||||||
|  | func (UnimplementedRoutingServiceServer) mustEmbedUnimplementedRoutingServiceServer() {} | ||||||
|  |  | ||||||
|  | // UnsafeRoutingServiceServer may be embedded to opt out of forward compatibility for this service. | ||||||
|  | // Use of this interface is not recommended, as added methods to RoutingServiceServer will | ||||||
|  | // result in compilation errors. | ||||||
|  | type UnsafeRoutingServiceServer interface { | ||||||
|  | 	mustEmbedUnimplementedRoutingServiceServer() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func RegisterRoutingServiceServer(s grpc.ServiceRegistrar, srv RoutingServiceServer) { | ||||||
|  | 	s.RegisterService(&_RoutingService_serviceDesc, srv) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func _RoutingService_SubscribeRoutingStats_Handler(srv interface{}, stream grpc.ServerStream) error { | ||||||
|  | 	m := new(SubscribeRoutingStatsRequest) | ||||||
|  | 	if err := stream.RecvMsg(m); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	return srv.(RoutingServiceServer).SubscribeRoutingStats(m, &routingServiceSubscribeRoutingStatsServer{stream}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type RoutingService_SubscribeRoutingStatsServer interface { | ||||||
|  | 	Send(*RoutingContext) error | ||||||
|  | 	grpc.ServerStream | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type routingServiceSubscribeRoutingStatsServer struct { | ||||||
|  | 	grpc.ServerStream | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *routingServiceSubscribeRoutingStatsServer) Send(m *RoutingContext) error { | ||||||
|  | 	return x.ServerStream.SendMsg(m) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func _RoutingService_TestRoute_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { | ||||||
|  | 	in := new(TestRouteRequest) | ||||||
|  | 	if err := dec(in); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	if interceptor == nil { | ||||||
|  | 		return srv.(RoutingServiceServer).TestRoute(ctx, in) | ||||||
|  | 	} | ||||||
|  | 	info := &grpc.UnaryServerInfo{ | ||||||
|  | 		Server:     srv, | ||||||
|  | 		FullMethod: "/xray.app.router.command.RoutingService/TestRoute", | ||||||
|  | 	} | ||||||
|  | 	handler := func(ctx context.Context, req interface{}) (interface{}, error) { | ||||||
|  | 		return srv.(RoutingServiceServer).TestRoute(ctx, req.(*TestRouteRequest)) | ||||||
|  | 	} | ||||||
|  | 	return interceptor(ctx, in, info, handler) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var _RoutingService_serviceDesc = grpc.ServiceDesc{ | ||||||
|  | 	ServiceName: "xray.app.router.command.RoutingService", | ||||||
|  | 	HandlerType: (*RoutingServiceServer)(nil), | ||||||
|  | 	Methods: []grpc.MethodDesc{ | ||||||
|  | 		{ | ||||||
|  | 			MethodName: "TestRoute", | ||||||
|  | 			Handler:    _RoutingService_TestRoute_Handler, | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | 	Streams: []grpc.StreamDesc{ | ||||||
|  | 		{ | ||||||
|  | 			StreamName:    "SubscribeRoutingStats", | ||||||
|  | 			Handler:       _RoutingService_SubscribeRoutingStats_Handler, | ||||||
|  | 			ServerStreams: true, | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | 	Metadata: "app/router/command/command.proto", | ||||||
|  | } | ||||||
							
								
								
									
										361
									
								
								app/router/command/command_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										361
									
								
								app/router/command/command_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,361 @@ | |||||||
|  | package command_test | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"testing" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/golang/mock/gomock" | ||||||
|  | 	"github.com/google/go-cmp/cmp" | ||||||
|  | 	"github.com/google/go-cmp/cmp/cmpopts" | ||||||
|  | 	"github.com/xtls/xray-core/v1/app/router" | ||||||
|  | 	. "github.com/xtls/xray-core/v1/app/router/command" | ||||||
|  | 	"github.com/xtls/xray-core/v1/app/stats" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/net" | ||||||
|  | 	"github.com/xtls/xray-core/v1/features/routing" | ||||||
|  | 	"github.com/xtls/xray-core/v1/testing/mocks" | ||||||
|  | 	"google.golang.org/grpc" | ||||||
|  | 	"google.golang.org/grpc/test/bufconn" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestServiceSubscribeRoutingStats(t *testing.T) { | ||||||
|  | 	c := stats.NewChannel(&stats.ChannelConfig{ | ||||||
|  | 		SubscriberLimit: 1, | ||||||
|  | 		BufferSize:      0, | ||||||
|  | 		Blocking:        true, | ||||||
|  | 	}) | ||||||
|  | 	common.Must(c.Start()) | ||||||
|  | 	defer c.Close() | ||||||
|  |  | ||||||
|  | 	lis := bufconn.Listen(1024 * 1024) | ||||||
|  | 	bufDialer := func(context.Context, string) (net.Conn, error) { | ||||||
|  | 		return lis.Dial() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	testCases := []*RoutingContext{ | ||||||
|  | 		{InboundTag: "in", OutboundTag: "out"}, | ||||||
|  | 		{TargetIPs: [][]byte{{1, 2, 3, 4}}, TargetPort: 8080, OutboundTag: "out"}, | ||||||
|  | 		{TargetDomain: "example.com", TargetPort: 443, OutboundTag: "out"}, | ||||||
|  | 		{SourcePort: 9999, TargetPort: 9999, OutboundTag: "out"}, | ||||||
|  | 		{Network: net.Network_UDP, OutboundGroupTags: []string{"outergroup", "innergroup"}, OutboundTag: "out"}, | ||||||
|  | 		{Protocol: "bittorrent", OutboundTag: "blocked"}, | ||||||
|  | 		{User: "example@example.com", OutboundTag: "out"}, | ||||||
|  | 		{SourceIPs: [][]byte{{127, 0, 0, 1}}, Attributes: map[string]string{"attr": "value"}, OutboundTag: "out"}, | ||||||
|  | 	} | ||||||
|  | 	errCh := make(chan error) | ||||||
|  | 	nextPub := make(chan struct{}) | ||||||
|  |  | ||||||
|  | 	// Server goroutine | ||||||
|  | 	go func() { | ||||||
|  | 		server := grpc.NewServer() | ||||||
|  | 		RegisterRoutingServiceServer(server, NewRoutingServer(nil, c)) | ||||||
|  | 		errCh <- server.Serve(lis) | ||||||
|  | 	}() | ||||||
|  |  | ||||||
|  | 	// Publisher goroutine | ||||||
|  | 	go func() { | ||||||
|  | 		publishTestCases := func() error { | ||||||
|  | 			ctx, cancel := context.WithTimeout(context.Background(), time.Second) | ||||||
|  | 			defer cancel() | ||||||
|  | 			for { // Wait until there's one subscriber in routing stats channel | ||||||
|  | 				if len(c.Subscribers()) > 0 { | ||||||
|  | 					break | ||||||
|  | 				} | ||||||
|  | 				if ctx.Err() != nil { | ||||||
|  | 					return ctx.Err() | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			for _, tc := range testCases { | ||||||
|  | 				c.Publish(context.Background(), AsRoutingRoute(tc)) | ||||||
|  | 				time.Sleep(time.Millisecond) | ||||||
|  | 			} | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if err := publishTestCases(); err != nil { | ||||||
|  | 			errCh <- err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Wait for next round of publishing | ||||||
|  | 		<-nextPub | ||||||
|  |  | ||||||
|  | 		if err := publishTestCases(); err != nil { | ||||||
|  | 			errCh <- err | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  |  | ||||||
|  | 	// Client goroutine | ||||||
|  | 	go func() { | ||||||
|  | 		defer lis.Close() | ||||||
|  | 		conn, err := grpc.DialContext(context.Background(), "bufnet", grpc.WithContextDialer(bufDialer), grpc.WithInsecure()) | ||||||
|  | 		if err != nil { | ||||||
|  | 			errCh <- err | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		defer conn.Close() | ||||||
|  | 		client := NewRoutingServiceClient(conn) | ||||||
|  |  | ||||||
|  | 		// Test retrieving all fields | ||||||
|  | 		testRetrievingAllFields := func() error { | ||||||
|  | 			streamCtx, streamClose := context.WithCancel(context.Background()) | ||||||
|  |  | ||||||
|  | 			// Test the unsubscription of stream works well | ||||||
|  | 			defer func() { | ||||||
|  | 				streamClose() | ||||||
|  | 				timeOutCtx, timeout := context.WithTimeout(context.Background(), time.Second) | ||||||
|  | 				defer timeout() | ||||||
|  | 				for { // Wait until there's no subscriber in routing stats channel | ||||||
|  | 					if len(c.Subscribers()) == 0 { | ||||||
|  | 						break | ||||||
|  | 					} | ||||||
|  | 					if timeOutCtx.Err() != nil { | ||||||
|  | 						t.Error("unexpected subscribers not decreased in channel", timeOutCtx.Err()) | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			}() | ||||||
|  |  | ||||||
|  | 			stream, err := client.SubscribeRoutingStats(streamCtx, &SubscribeRoutingStatsRequest{}) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			for _, tc := range testCases { | ||||||
|  | 				msg, err := stream.Recv() | ||||||
|  | 				if err != nil { | ||||||
|  | 					return err | ||||||
|  | 				} | ||||||
|  | 				if r := cmp.Diff(msg, tc, cmpopts.IgnoreUnexported(RoutingContext{})); r != "" { | ||||||
|  | 					t.Error(r) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Test that double subscription will fail | ||||||
|  | 			errStream, err := client.SubscribeRoutingStats(context.Background(), &SubscribeRoutingStatsRequest{ | ||||||
|  | 				FieldSelectors: []string{"ip", "port", "domain", "outbound"}, | ||||||
|  | 			}) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			if _, err := errStream.Recv(); err == nil { | ||||||
|  | 				t.Error("unexpected successful subscription") | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Test retrieving only a subset of fields | ||||||
|  | 		testRetrievingSubsetOfFields := func() error { | ||||||
|  | 			streamCtx, streamClose := context.WithCancel(context.Background()) | ||||||
|  | 			defer streamClose() | ||||||
|  | 			stream, err := client.SubscribeRoutingStats(streamCtx, &SubscribeRoutingStatsRequest{ | ||||||
|  | 				FieldSelectors: []string{"ip", "port", "domain", "outbound"}, | ||||||
|  | 			}) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Send nextPub signal to start next round of publishing | ||||||
|  | 			close(nextPub) | ||||||
|  |  | ||||||
|  | 			for _, tc := range testCases { | ||||||
|  | 				msg, err := stream.Recv() | ||||||
|  | 				if err != nil { | ||||||
|  | 					return err | ||||||
|  | 				} | ||||||
|  | 				stat := &RoutingContext{ // Only a subset of stats is retrieved | ||||||
|  | 					SourceIPs:         tc.SourceIPs, | ||||||
|  | 					TargetIPs:         tc.TargetIPs, | ||||||
|  | 					SourcePort:        tc.SourcePort, | ||||||
|  | 					TargetPort:        tc.TargetPort, | ||||||
|  | 					TargetDomain:      tc.TargetDomain, | ||||||
|  | 					OutboundGroupTags: tc.OutboundGroupTags, | ||||||
|  | 					OutboundTag:       tc.OutboundTag, | ||||||
|  | 				} | ||||||
|  | 				if r := cmp.Diff(msg, stat, cmpopts.IgnoreUnexported(RoutingContext{})); r != "" { | ||||||
|  | 					t.Error(r) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if err := testRetrievingAllFields(); err != nil { | ||||||
|  | 			errCh <- err | ||||||
|  | 		} | ||||||
|  | 		if err := testRetrievingSubsetOfFields(); err != nil { | ||||||
|  | 			errCh <- err | ||||||
|  | 		} | ||||||
|  | 		errCh <- nil // Client passed all tests successfully | ||||||
|  | 	}() | ||||||
|  |  | ||||||
|  | 	// Wait for goroutines to complete | ||||||
|  | 	select { | ||||||
|  | 	case <-time.After(2 * time.Second): | ||||||
|  | 		t.Fatal("Test timeout after 2s") | ||||||
|  | 	case err := <-errCh: | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatal(err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestSerivceTestRoute(t *testing.T) { | ||||||
|  | 	c := stats.NewChannel(&stats.ChannelConfig{ | ||||||
|  | 		SubscriberLimit: 1, | ||||||
|  | 		BufferSize:      16, | ||||||
|  | 		Blocking:        true, | ||||||
|  | 	}) | ||||||
|  | 	common.Must(c.Start()) | ||||||
|  | 	defer c.Close() | ||||||
|  |  | ||||||
|  | 	r := new(router.Router) | ||||||
|  | 	mockCtl := gomock.NewController(t) | ||||||
|  | 	defer mockCtl.Finish() | ||||||
|  | 	common.Must(r.Init(&router.Config{ | ||||||
|  | 		Rule: []*router.RoutingRule{ | ||||||
|  | 			{ | ||||||
|  | 				InboundTag: []string{"in"}, | ||||||
|  | 				TargetTag:  &router.RoutingRule_Tag{Tag: "out"}, | ||||||
|  | 			}, | ||||||
|  | 			{ | ||||||
|  | 				Protocol:  []string{"bittorrent"}, | ||||||
|  | 				TargetTag: &router.RoutingRule_Tag{Tag: "blocked"}, | ||||||
|  | 			}, | ||||||
|  | 			{ | ||||||
|  | 				PortList:  &net.PortList{Range: []*net.PortRange{{From: 8080, To: 8080}}}, | ||||||
|  | 				TargetTag: &router.RoutingRule_Tag{Tag: "out"}, | ||||||
|  | 			}, | ||||||
|  | 			{ | ||||||
|  | 				SourcePortList: &net.PortList{Range: []*net.PortRange{{From: 9999, To: 9999}}}, | ||||||
|  | 				TargetTag:      &router.RoutingRule_Tag{Tag: "out"}, | ||||||
|  | 			}, | ||||||
|  | 			{ | ||||||
|  | 				Domain:    []*router.Domain{{Type: router.Domain_Domain, Value: "com"}}, | ||||||
|  | 				TargetTag: &router.RoutingRule_Tag{Tag: "out"}, | ||||||
|  | 			}, | ||||||
|  | 			{ | ||||||
|  | 				SourceGeoip: []*router.GeoIP{{CountryCode: "private", Cidr: []*router.CIDR{{Ip: []byte{127, 0, 0, 0}, Prefix: 8}}}}, | ||||||
|  | 				TargetTag:   &router.RoutingRule_Tag{Tag: "out"}, | ||||||
|  | 			}, | ||||||
|  | 			{ | ||||||
|  | 				UserEmail: []string{"example@example.com"}, | ||||||
|  | 				TargetTag: &router.RoutingRule_Tag{Tag: "out"}, | ||||||
|  | 			}, | ||||||
|  | 			{ | ||||||
|  | 				Networks:  []net.Network{net.Network_UDP, net.Network_TCP}, | ||||||
|  | 				TargetTag: &router.RoutingRule_Tag{Tag: "out"}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	}, mocks.NewDNSClient(mockCtl), mocks.NewOutboundManager(mockCtl))) | ||||||
|  |  | ||||||
|  | 	lis := bufconn.Listen(1024 * 1024) | ||||||
|  | 	bufDialer := func(context.Context, string) (net.Conn, error) { | ||||||
|  | 		return lis.Dial() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	errCh := make(chan error) | ||||||
|  |  | ||||||
|  | 	// Server goroutine | ||||||
|  | 	go func() { | ||||||
|  | 		server := grpc.NewServer() | ||||||
|  | 		RegisterRoutingServiceServer(server, NewRoutingServer(r, c)) | ||||||
|  | 		errCh <- server.Serve(lis) | ||||||
|  | 	}() | ||||||
|  |  | ||||||
|  | 	// Client goroutine | ||||||
|  | 	go func() { | ||||||
|  | 		defer lis.Close() | ||||||
|  | 		conn, err := grpc.DialContext(context.Background(), "bufnet", grpc.WithContextDialer(bufDialer), grpc.WithInsecure()) | ||||||
|  | 		if err != nil { | ||||||
|  | 			errCh <- err | ||||||
|  | 		} | ||||||
|  | 		defer conn.Close() | ||||||
|  | 		client := NewRoutingServiceClient(conn) | ||||||
|  |  | ||||||
|  | 		testCases := []*RoutingContext{ | ||||||
|  | 			{InboundTag: "in", OutboundTag: "out"}, | ||||||
|  | 			{TargetIPs: [][]byte{{1, 2, 3, 4}}, TargetPort: 8080, OutboundTag: "out"}, | ||||||
|  | 			{TargetDomain: "example.com", TargetPort: 443, OutboundTag: "out"}, | ||||||
|  | 			{SourcePort: 9999, TargetPort: 9999, OutboundTag: "out"}, | ||||||
|  | 			{Network: net.Network_UDP, Protocol: "bittorrent", OutboundTag: "blocked"}, | ||||||
|  | 			{User: "example@example.com", OutboundTag: "out"}, | ||||||
|  | 			{SourceIPs: [][]byte{{127, 0, 0, 1}}, Attributes: map[string]string{"attr": "value"}, OutboundTag: "out"}, | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Test simple TestRoute | ||||||
|  | 		testSimple := func() error { | ||||||
|  | 			for _, tc := range testCases { | ||||||
|  | 				route, err := client.TestRoute(context.Background(), &TestRouteRequest{RoutingContext: tc}) | ||||||
|  | 				if err != nil { | ||||||
|  | 					return err | ||||||
|  | 				} | ||||||
|  | 				if r := cmp.Diff(route, tc, cmpopts.IgnoreUnexported(RoutingContext{})); r != "" { | ||||||
|  | 					t.Error(r) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Test TestRoute with special options | ||||||
|  | 		testOptions := func() error { | ||||||
|  | 			sub, err := c.Subscribe() | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			for _, tc := range testCases { | ||||||
|  | 				route, err := client.TestRoute(context.Background(), &TestRouteRequest{ | ||||||
|  | 					RoutingContext: tc, | ||||||
|  | 					FieldSelectors: []string{"ip", "port", "domain", "outbound"}, | ||||||
|  | 					PublishResult:  true, | ||||||
|  | 				}) | ||||||
|  | 				if err != nil { | ||||||
|  | 					return err | ||||||
|  | 				} | ||||||
|  | 				stat := &RoutingContext{ // Only a subset of stats is retrieved | ||||||
|  | 					SourceIPs:         tc.SourceIPs, | ||||||
|  | 					TargetIPs:         tc.TargetIPs, | ||||||
|  | 					SourcePort:        tc.SourcePort, | ||||||
|  | 					TargetPort:        tc.TargetPort, | ||||||
|  | 					TargetDomain:      tc.TargetDomain, | ||||||
|  | 					OutboundGroupTags: tc.OutboundGroupTags, | ||||||
|  | 					OutboundTag:       tc.OutboundTag, | ||||||
|  | 				} | ||||||
|  | 				if r := cmp.Diff(route, stat, cmpopts.IgnoreUnexported(RoutingContext{})); r != "" { | ||||||
|  | 					t.Error(r) | ||||||
|  | 				} | ||||||
|  | 				select { // Check that routing result has been published to statistics channel | ||||||
|  | 				case msg, received := <-sub: | ||||||
|  | 					if route, ok := msg.(routing.Route); received && ok { | ||||||
|  | 						if r := cmp.Diff(AsProtobufMessage(nil)(route), tc, cmpopts.IgnoreUnexported(RoutingContext{})); r != "" { | ||||||
|  | 							t.Error(r) | ||||||
|  | 						} | ||||||
|  | 					} else { | ||||||
|  | 						t.Error("unexpected failure in receiving published routing result for testcase", tc) | ||||||
|  | 					} | ||||||
|  | 				case <-time.After(100 * time.Millisecond): | ||||||
|  | 					t.Error("unexpected failure in receiving published routing result", tc) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if err := testSimple(); err != nil { | ||||||
|  | 			errCh <- err | ||||||
|  | 		} | ||||||
|  | 		if err := testOptions(); err != nil { | ||||||
|  | 			errCh <- err | ||||||
|  | 		} | ||||||
|  | 		errCh <- nil // Client passed all tests successfully | ||||||
|  | 	}() | ||||||
|  |  | ||||||
|  | 	// Wait for goroutines to complete | ||||||
|  | 	select { | ||||||
|  | 	case <-time.After(2 * time.Second): | ||||||
|  | 		t.Fatal("Test timeout after 2s") | ||||||
|  | 	case err := <-errCh: | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatal(err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										94
									
								
								app/router/command/config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								app/router/command/config.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,94 @@ | |||||||
|  | package command | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"strings" | ||||||
|  |  | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/net" | ||||||
|  | 	"github.com/xtls/xray-core/v1/features/routing" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // routingContext is an wrapper of protobuf RoutingContext as implementation of routing.Context and routing.Route. | ||||||
|  | type routingContext struct { | ||||||
|  | 	*RoutingContext | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c routingContext) GetSourceIPs() []net.IP { | ||||||
|  | 	return mapBytesToIPs(c.RoutingContext.GetSourceIPs()) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c routingContext) GetSourcePort() net.Port { | ||||||
|  | 	return net.Port(c.RoutingContext.GetSourcePort()) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c routingContext) GetTargetIPs() []net.IP { | ||||||
|  | 	return mapBytesToIPs(c.RoutingContext.GetTargetIPs()) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c routingContext) GetTargetPort() net.Port { | ||||||
|  | 	return net.Port(c.RoutingContext.GetTargetPort()) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // AsRoutingContext converts a protobuf RoutingContext into an implementation of routing.Context. | ||||||
|  | func AsRoutingContext(r *RoutingContext) routing.Context { | ||||||
|  | 	return routingContext{r} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // AsRoutingRoute converts a protobuf RoutingContext into an implementation of routing.Route. | ||||||
|  | func AsRoutingRoute(r *RoutingContext) routing.Route { | ||||||
|  | 	return routingContext{r} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var fieldMap = map[string]func(*RoutingContext, routing.Route){ | ||||||
|  | 	"inbound":        func(s *RoutingContext, r routing.Route) { s.InboundTag = r.GetInboundTag() }, | ||||||
|  | 	"network":        func(s *RoutingContext, r routing.Route) { s.Network = r.GetNetwork() }, | ||||||
|  | 	"ip_source":      func(s *RoutingContext, r routing.Route) { s.SourceIPs = mapIPsToBytes(r.GetSourceIPs()) }, | ||||||
|  | 	"ip_target":      func(s *RoutingContext, r routing.Route) { s.TargetIPs = mapIPsToBytes(r.GetTargetIPs()) }, | ||||||
|  | 	"port_source":    func(s *RoutingContext, r routing.Route) { s.SourcePort = uint32(r.GetSourcePort()) }, | ||||||
|  | 	"port_target":    func(s *RoutingContext, r routing.Route) { s.TargetPort = uint32(r.GetTargetPort()) }, | ||||||
|  | 	"domain":         func(s *RoutingContext, r routing.Route) { s.TargetDomain = r.GetTargetDomain() }, | ||||||
|  | 	"protocol":       func(s *RoutingContext, r routing.Route) { s.Protocol = r.GetProtocol() }, | ||||||
|  | 	"user":           func(s *RoutingContext, r routing.Route) { s.User = r.GetUser() }, | ||||||
|  | 	"attributes":     func(s *RoutingContext, r routing.Route) { s.Attributes = r.GetAttributes() }, | ||||||
|  | 	"outbound_group": func(s *RoutingContext, r routing.Route) { s.OutboundGroupTags = r.GetOutboundGroupTags() }, | ||||||
|  | 	"outbound":       func(s *RoutingContext, r routing.Route) { s.OutboundTag = r.GetOutboundTag() }, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // AsProtobufMessage takes selectors of fields and returns a function to convert routing.Route to protobuf RoutingContext. | ||||||
|  | func AsProtobufMessage(fieldSelectors []string) func(routing.Route) *RoutingContext { | ||||||
|  | 	initializers := []func(*RoutingContext, routing.Route){} | ||||||
|  | 	for field, init := range fieldMap { | ||||||
|  | 		if len(fieldSelectors) == 0 { // If selectors not set, retrieve all fields | ||||||
|  | 			initializers = append(initializers, init) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		for _, selector := range fieldSelectors { | ||||||
|  | 			if strings.HasPrefix(field, selector) { | ||||||
|  | 				initializers = append(initializers, init) | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return func(ctx routing.Route) *RoutingContext { | ||||||
|  | 		message := new(RoutingContext) | ||||||
|  | 		for _, init := range initializers { | ||||||
|  | 			init(message, ctx) | ||||||
|  | 		} | ||||||
|  | 		return message | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func mapBytesToIPs(bytes [][]byte) []net.IP { | ||||||
|  | 	var ips []net.IP | ||||||
|  | 	for _, rawIP := range bytes { | ||||||
|  | 		ips = append(ips, net.IP(rawIP)) | ||||||
|  | 	} | ||||||
|  | 	return ips | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func mapIPsToBytes(ips []net.IP) [][]byte { | ||||||
|  | 	var bytes [][]byte | ||||||
|  | 	for _, ip := range ips { | ||||||
|  | 		bytes = append(bytes, []byte(ip)) | ||||||
|  | 	} | ||||||
|  | 	return bytes | ||||||
|  | } | ||||||
							
								
								
									
										9
									
								
								app/router/command/errors.generated.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								app/router/command/errors.generated.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | package command | ||||||
|  |  | ||||||
|  | import "github.com/xtls/xray-core/v1/common/errors" | ||||||
|  |  | ||||||
|  | type errPathObjHolder struct{} | ||||||
|  |  | ||||||
|  | func newError(values ...interface{}) *errors.Error { | ||||||
|  | 	return errors.New(values...).WithPathObj(errPathObjHolder{}) | ||||||
|  | } | ||||||
							
								
								
									
										319
									
								
								app/router/condition.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										319
									
								
								app/router/condition.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,319 @@ | |||||||
|  | // +build !confonly | ||||||
|  |  | ||||||
|  | package router | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"strings" | ||||||
|  |  | ||||||
|  | 	"go.starlark.net/starlark" | ||||||
|  | 	"go.starlark.net/syntax" | ||||||
|  |  | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/net" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/strmatcher" | ||||||
|  | 	"github.com/xtls/xray-core/v1/features/routing" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type Condition interface { | ||||||
|  | 	Apply(ctx routing.Context) bool | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type ConditionChan []Condition | ||||||
|  |  | ||||||
|  | func NewConditionChan() *ConditionChan { | ||||||
|  | 	var condChan ConditionChan = make([]Condition, 0, 8) | ||||||
|  | 	return &condChan | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (v *ConditionChan) Add(cond Condition) *ConditionChan { | ||||||
|  | 	*v = append(*v, cond) | ||||||
|  | 	return v | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Apply applies all conditions registered in this chan. | ||||||
|  | func (v *ConditionChan) Apply(ctx routing.Context) bool { | ||||||
|  | 	for _, cond := range *v { | ||||||
|  | 		if !cond.Apply(ctx) { | ||||||
|  | 			return false | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return true | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (v *ConditionChan) Len() int { | ||||||
|  | 	return len(*v) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var matcherTypeMap = map[Domain_Type]strmatcher.Type{ | ||||||
|  | 	Domain_Plain:  strmatcher.Substr, | ||||||
|  | 	Domain_Regex:  strmatcher.Regex, | ||||||
|  | 	Domain_Domain: strmatcher.Domain, | ||||||
|  | 	Domain_Full:   strmatcher.Full, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func domainToMatcher(domain *Domain) (strmatcher.Matcher, error) { | ||||||
|  | 	matcherType, f := matcherTypeMap[domain.Type] | ||||||
|  | 	if !f { | ||||||
|  | 		return nil, newError("unsupported domain type", domain.Type) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	matcher, err := matcherType.New(domain.Value) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, newError("failed to create domain matcher").Base(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return matcher, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type DomainMatcher struct { | ||||||
|  | 	matchers strmatcher.IndexMatcher | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewDomainMatcher(domains []*Domain) (*DomainMatcher, error) { | ||||||
|  | 	g := new(strmatcher.MatcherGroup) | ||||||
|  | 	for _, d := range domains { | ||||||
|  | 		m, err := domainToMatcher(d) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		g.Add(m) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return &DomainMatcher{ | ||||||
|  | 		matchers: g, | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (m *DomainMatcher) ApplyDomain(domain string) bool { | ||||||
|  | 	return len(m.matchers.Match(domain)) > 0 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Apply implements Condition. | ||||||
|  | func (m *DomainMatcher) Apply(ctx routing.Context) bool { | ||||||
|  | 	domain := ctx.GetTargetDomain() | ||||||
|  | 	if len(domain) == 0 { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	return m.ApplyDomain(domain) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type MultiGeoIPMatcher struct { | ||||||
|  | 	matchers []*GeoIPMatcher | ||||||
|  | 	onSource bool | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewMultiGeoIPMatcher(geoips []*GeoIP, onSource bool) (*MultiGeoIPMatcher, error) { | ||||||
|  | 	var matchers []*GeoIPMatcher | ||||||
|  | 	for _, geoip := range geoips { | ||||||
|  | 		matcher, err := globalGeoIPContainer.Add(geoip) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		matchers = append(matchers, matcher) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	matcher := &MultiGeoIPMatcher{ | ||||||
|  | 		matchers: matchers, | ||||||
|  | 		onSource: onSource, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return matcher, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Apply implements Condition. | ||||||
|  | func (m *MultiGeoIPMatcher) Apply(ctx routing.Context) bool { | ||||||
|  | 	var ips []net.IP | ||||||
|  | 	if m.onSource { | ||||||
|  | 		ips = ctx.GetSourceIPs() | ||||||
|  | 	} else { | ||||||
|  | 		ips = ctx.GetTargetIPs() | ||||||
|  | 	} | ||||||
|  | 	for _, ip := range ips { | ||||||
|  | 		for _, matcher := range m.matchers { | ||||||
|  | 			if matcher.Match(ip) { | ||||||
|  | 				return true | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type PortMatcher struct { | ||||||
|  | 	port     net.MemoryPortList | ||||||
|  | 	onSource bool | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // NewPortMatcher create a new port matcher that can match source or destination port | ||||||
|  | func NewPortMatcher(list *net.PortList, onSource bool) *PortMatcher { | ||||||
|  | 	return &PortMatcher{ | ||||||
|  | 		port:     net.PortListFromProto(list), | ||||||
|  | 		onSource: onSource, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Apply implements Condition. | ||||||
|  | func (v *PortMatcher) Apply(ctx routing.Context) bool { | ||||||
|  | 	if v.onSource { | ||||||
|  | 		return v.port.Contains(ctx.GetSourcePort()) | ||||||
|  | 	} else { | ||||||
|  | 		return v.port.Contains(ctx.GetTargetPort()) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type NetworkMatcher struct { | ||||||
|  | 	list [8]bool | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewNetworkMatcher(network []net.Network) NetworkMatcher { | ||||||
|  | 	var matcher NetworkMatcher | ||||||
|  | 	for _, n := range network { | ||||||
|  | 		matcher.list[int(n)] = true | ||||||
|  | 	} | ||||||
|  | 	return matcher | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Apply implements Condition. | ||||||
|  | func (v NetworkMatcher) Apply(ctx routing.Context) bool { | ||||||
|  | 	return v.list[int(ctx.GetNetwork())] | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type UserMatcher struct { | ||||||
|  | 	user []string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewUserMatcher(users []string) *UserMatcher { | ||||||
|  | 	usersCopy := make([]string, 0, len(users)) | ||||||
|  | 	for _, user := range users { | ||||||
|  | 		if len(user) > 0 { | ||||||
|  | 			usersCopy = append(usersCopy, user) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return &UserMatcher{ | ||||||
|  | 		user: usersCopy, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Apply implements Condition. | ||||||
|  | func (v *UserMatcher) Apply(ctx routing.Context) bool { | ||||||
|  | 	user := ctx.GetUser() | ||||||
|  | 	if len(user) == 0 { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	for _, u := range v.user { | ||||||
|  | 		if u == user { | ||||||
|  | 			return true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type InboundTagMatcher struct { | ||||||
|  | 	tags []string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewInboundTagMatcher(tags []string) *InboundTagMatcher { | ||||||
|  | 	tagsCopy := make([]string, 0, len(tags)) | ||||||
|  | 	for _, tag := range tags { | ||||||
|  | 		if len(tag) > 0 { | ||||||
|  | 			tagsCopy = append(tagsCopy, tag) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return &InboundTagMatcher{ | ||||||
|  | 		tags: tagsCopy, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Apply implements Condition. | ||||||
|  | func (v *InboundTagMatcher) Apply(ctx routing.Context) bool { | ||||||
|  | 	tag := ctx.GetInboundTag() | ||||||
|  | 	if len(tag) == 0 { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	for _, t := range v.tags { | ||||||
|  | 		if t == tag { | ||||||
|  | 			return true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type ProtocolMatcher struct { | ||||||
|  | 	protocols []string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewProtocolMatcher(protocols []string) *ProtocolMatcher { | ||||||
|  | 	pCopy := make([]string, 0, len(protocols)) | ||||||
|  |  | ||||||
|  | 	for _, p := range protocols { | ||||||
|  | 		if len(p) > 0 { | ||||||
|  | 			pCopy = append(pCopy, p) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return &ProtocolMatcher{ | ||||||
|  | 		protocols: pCopy, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Apply implements Condition. | ||||||
|  | func (m *ProtocolMatcher) Apply(ctx routing.Context) bool { | ||||||
|  | 	protocol := ctx.GetProtocol() | ||||||
|  | 	if len(protocol) == 0 { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	for _, p := range m.protocols { | ||||||
|  | 		if strings.HasPrefix(protocol, p) { | ||||||
|  | 			return true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type AttributeMatcher struct { | ||||||
|  | 	program *starlark.Program | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewAttributeMatcher(code string) (*AttributeMatcher, error) { | ||||||
|  | 	starFile, err := syntax.Parse("attr.star", "satisfied=("+code+")", 0) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, newError("attr rule").Base(err) | ||||||
|  | 	} | ||||||
|  | 	p, err := starlark.FileProgram(starFile, func(name string) bool { | ||||||
|  | 		return name == "attrs" | ||||||
|  | 	}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return &AttributeMatcher{ | ||||||
|  | 		program: p, | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Match implements attributes matching. | ||||||
|  | func (m *AttributeMatcher) Match(attrs map[string]string) bool { | ||||||
|  | 	attrsDict := new(starlark.Dict) | ||||||
|  | 	for key, value := range attrs { | ||||||
|  | 		attrsDict.SetKey(starlark.String(key), starlark.String(value)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	predefined := make(starlark.StringDict) | ||||||
|  | 	predefined["attrs"] = attrsDict | ||||||
|  |  | ||||||
|  | 	thread := &starlark.Thread{ | ||||||
|  | 		Name: "matcher", | ||||||
|  | 	} | ||||||
|  | 	results, err := m.program.Init(thread, predefined) | ||||||
|  | 	if err != nil { | ||||||
|  | 		newError("attr matcher").Base(err).WriteToLog() | ||||||
|  | 	} | ||||||
|  | 	satisfied := results["satisfied"] | ||||||
|  | 	return satisfied != nil && bool(satisfied.Truth()) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Apply implements Condition. | ||||||
|  | func (m *AttributeMatcher) Apply(ctx routing.Context) bool { | ||||||
|  | 	attributes := ctx.GetAttributes() | ||||||
|  | 	if attributes == nil { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	return m.Match(attributes) | ||||||
|  | } | ||||||
							
								
								
									
										193
									
								
								app/router/condition_geoip.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										193
									
								
								app/router/condition_geoip.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,193 @@ | |||||||
|  | // +build !confonly | ||||||
|  |  | ||||||
|  | package router | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"encoding/binary" | ||||||
|  | 	"sort" | ||||||
|  |  | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/net" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type ipv6 struct { | ||||||
|  | 	a uint64 | ||||||
|  | 	b uint64 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type GeoIPMatcher struct { | ||||||
|  | 	countryCode string | ||||||
|  | 	ip4         []uint32 | ||||||
|  | 	prefix4     []uint8 | ||||||
|  | 	ip6         []ipv6 | ||||||
|  | 	prefix6     []uint8 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func normalize4(ip uint32, prefix uint8) uint32 { | ||||||
|  | 	return (ip >> (32 - prefix)) << (32 - prefix) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func normalize6(ip ipv6, prefix uint8) ipv6 { | ||||||
|  | 	if prefix <= 64 { | ||||||
|  | 		ip.a = (ip.a >> (64 - prefix)) << (64 - prefix) | ||||||
|  | 		ip.b = 0 | ||||||
|  | 	} else { | ||||||
|  | 		ip.b = (ip.b >> (128 - prefix)) << (128 - prefix) | ||||||
|  | 	} | ||||||
|  | 	return ip | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (m *GeoIPMatcher) Init(cidrs []*CIDR) error { | ||||||
|  | 	ip4Count := 0 | ||||||
|  | 	ip6Count := 0 | ||||||
|  |  | ||||||
|  | 	for _, cidr := range cidrs { | ||||||
|  | 		ip := cidr.Ip | ||||||
|  | 		switch len(ip) { | ||||||
|  | 		case 4: | ||||||
|  | 			ip4Count++ | ||||||
|  | 		case 16: | ||||||
|  | 			ip6Count++ | ||||||
|  | 		default: | ||||||
|  | 			return newError("unexpect ip length: ", len(ip)) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	cidrList := CIDRList(cidrs) | ||||||
|  | 	sort.Sort(&cidrList) | ||||||
|  |  | ||||||
|  | 	m.ip4 = make([]uint32, 0, ip4Count) | ||||||
|  | 	m.prefix4 = make([]uint8, 0, ip4Count) | ||||||
|  | 	m.ip6 = make([]ipv6, 0, ip6Count) | ||||||
|  | 	m.prefix6 = make([]uint8, 0, ip6Count) | ||||||
|  |  | ||||||
|  | 	for _, cidr := range cidrs { | ||||||
|  | 		ip := cidr.Ip | ||||||
|  | 		prefix := uint8(cidr.Prefix) | ||||||
|  | 		switch len(ip) { | ||||||
|  | 		case 4: | ||||||
|  | 			m.ip4 = append(m.ip4, normalize4(binary.BigEndian.Uint32(ip), prefix)) | ||||||
|  | 			m.prefix4 = append(m.prefix4, prefix) | ||||||
|  | 		case 16: | ||||||
|  | 			ip6 := ipv6{ | ||||||
|  | 				a: binary.BigEndian.Uint64(ip[0:8]), | ||||||
|  | 				b: binary.BigEndian.Uint64(ip[8:16]), | ||||||
|  | 			} | ||||||
|  | 			ip6 = normalize6(ip6, prefix) | ||||||
|  |  | ||||||
|  | 			m.ip6 = append(m.ip6, ip6) | ||||||
|  | 			m.prefix6 = append(m.prefix6, prefix) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (m *GeoIPMatcher) match4(ip uint32) bool { | ||||||
|  | 	if len(m.ip4) == 0 { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if ip < m.ip4[0] { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	size := uint32(len(m.ip4)) | ||||||
|  | 	l := uint32(0) | ||||||
|  | 	r := size | ||||||
|  | 	for l < r { | ||||||
|  | 		x := ((l + r) >> 1) | ||||||
|  | 		if ip < m.ip4[x] { | ||||||
|  | 			r = x | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		nip := normalize4(ip, m.prefix4[x]) | ||||||
|  | 		if nip == m.ip4[x] { | ||||||
|  | 			return true | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		l = x + 1 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return l > 0 && normalize4(ip, m.prefix4[l-1]) == m.ip4[l-1] | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func less6(a ipv6, b ipv6) bool { | ||||||
|  | 	return a.a < b.a || (a.a == b.a && a.b < b.b) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (m *GeoIPMatcher) match6(ip ipv6) bool { | ||||||
|  | 	if len(m.ip6) == 0 { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if less6(ip, m.ip6[0]) { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	size := uint32(len(m.ip6)) | ||||||
|  | 	l := uint32(0) | ||||||
|  | 	r := size | ||||||
|  | 	for l < r { | ||||||
|  | 		x := (l + r) / 2 | ||||||
|  | 		if less6(ip, m.ip6[x]) { | ||||||
|  | 			r = x | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if normalize6(ip, m.prefix6[x]) == m.ip6[x] { | ||||||
|  | 			return true | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		l = x + 1 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return l > 0 && normalize6(ip, m.prefix6[l-1]) == m.ip6[l-1] | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Match returns true if the given ip is included by the GeoIP. | ||||||
|  | func (m *GeoIPMatcher) Match(ip net.IP) bool { | ||||||
|  | 	switch len(ip) { | ||||||
|  | 	case 4: | ||||||
|  | 		return m.match4(binary.BigEndian.Uint32(ip)) | ||||||
|  | 	case 16: | ||||||
|  | 		return m.match6(ipv6{ | ||||||
|  | 			a: binary.BigEndian.Uint64(ip[0:8]), | ||||||
|  | 			b: binary.BigEndian.Uint64(ip[8:16]), | ||||||
|  | 		}) | ||||||
|  | 	default: | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // GeoIPMatcherContainer is a container for GeoIPMatchers. It keeps unique copies of GeoIPMatcher by country code. | ||||||
|  | type GeoIPMatcherContainer struct { | ||||||
|  | 	matchers []*GeoIPMatcher | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Add adds a new GeoIP set into the container. | ||||||
|  | // If the country code of GeoIP is not empty, GeoIPMatcherContainer will try to find an existing one, instead of adding a new one. | ||||||
|  | func (c *GeoIPMatcherContainer) Add(geoip *GeoIP) (*GeoIPMatcher, error) { | ||||||
|  | 	if len(geoip.CountryCode) > 0 { | ||||||
|  | 		for _, m := range c.matchers { | ||||||
|  | 			if m.countryCode == geoip.CountryCode { | ||||||
|  | 				return m, nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	m := &GeoIPMatcher{ | ||||||
|  | 		countryCode: geoip.CountryCode, | ||||||
|  | 	} | ||||||
|  | 	if err := m.Init(geoip.Cidr); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	if len(geoip.CountryCode) > 0 { | ||||||
|  | 		c.matchers = append(c.matchers, m) | ||||||
|  | 	} | ||||||
|  | 	return m, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var ( | ||||||
|  | 	globalGeoIPContainer GeoIPMatcherContainer | ||||||
|  | ) | ||||||
							
								
								
									
										195
									
								
								app/router/condition_geoip_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										195
									
								
								app/router/condition_geoip_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,195 @@ | |||||||
|  | package router_test | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"os" | ||||||
|  | 	"path/filepath" | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"github.com/golang/protobuf/proto" | ||||||
|  | 	"github.com/xtls/xray-core/v1/app/router" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/net" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/platform" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/platform/filesystem" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func init() { | ||||||
|  | 	wd, err := os.Getwd() | ||||||
|  | 	common.Must(err) | ||||||
|  |  | ||||||
|  | 	if _, err := os.Stat(platform.GetAssetLocation("geoip.dat")); err != nil && os.IsNotExist(err) { | ||||||
|  | 		common.Must(filesystem.CopyFile(platform.GetAssetLocation("geoip.dat"), filepath.Join(wd, "..", "..", "release", "config", "geoip.dat"))) | ||||||
|  | 	} | ||||||
|  | 	if _, err := os.Stat(platform.GetAssetLocation("geosite.dat")); err != nil && os.IsNotExist(err) { | ||||||
|  | 		common.Must(filesystem.CopyFile(platform.GetAssetLocation("geosite.dat"), filepath.Join(wd, "..", "..", "release", "config", "geosite.dat"))) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestGeoIPMatcherContainer(t *testing.T) { | ||||||
|  | 	container := &router.GeoIPMatcherContainer{} | ||||||
|  |  | ||||||
|  | 	m1, err := container.Add(&router.GeoIP{ | ||||||
|  | 		CountryCode: "CN", | ||||||
|  | 	}) | ||||||
|  | 	common.Must(err) | ||||||
|  |  | ||||||
|  | 	m2, err := container.Add(&router.GeoIP{ | ||||||
|  | 		CountryCode: "US", | ||||||
|  | 	}) | ||||||
|  | 	common.Must(err) | ||||||
|  |  | ||||||
|  | 	m3, err := container.Add(&router.GeoIP{ | ||||||
|  | 		CountryCode: "CN", | ||||||
|  | 	}) | ||||||
|  | 	common.Must(err) | ||||||
|  |  | ||||||
|  | 	if m1 != m3 { | ||||||
|  | 		t.Error("expect same matcher for same geoip, but not") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if m1 == m2 { | ||||||
|  | 		t.Error("expect different matcher for different geoip, but actually same") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestGeoIPMatcher(t *testing.T) { | ||||||
|  | 	cidrList := router.CIDRList{ | ||||||
|  | 		{Ip: []byte{0, 0, 0, 0}, Prefix: 8}, | ||||||
|  | 		{Ip: []byte{10, 0, 0, 0}, Prefix: 8}, | ||||||
|  | 		{Ip: []byte{100, 64, 0, 0}, Prefix: 10}, | ||||||
|  | 		{Ip: []byte{127, 0, 0, 0}, Prefix: 8}, | ||||||
|  | 		{Ip: []byte{169, 254, 0, 0}, Prefix: 16}, | ||||||
|  | 		{Ip: []byte{172, 16, 0, 0}, Prefix: 12}, | ||||||
|  | 		{Ip: []byte{192, 0, 0, 0}, Prefix: 24}, | ||||||
|  | 		{Ip: []byte{192, 0, 2, 0}, Prefix: 24}, | ||||||
|  | 		{Ip: []byte{192, 168, 0, 0}, Prefix: 16}, | ||||||
|  | 		{Ip: []byte{192, 18, 0, 0}, Prefix: 15}, | ||||||
|  | 		{Ip: []byte{198, 51, 100, 0}, Prefix: 24}, | ||||||
|  | 		{Ip: []byte{203, 0, 113, 0}, Prefix: 24}, | ||||||
|  | 		{Ip: []byte{8, 8, 8, 8}, Prefix: 32}, | ||||||
|  | 		{Ip: []byte{91, 108, 4, 0}, Prefix: 16}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	matcher := &router.GeoIPMatcher{} | ||||||
|  | 	common.Must(matcher.Init(cidrList)) | ||||||
|  |  | ||||||
|  | 	testCases := []struct { | ||||||
|  | 		Input  string | ||||||
|  | 		Output bool | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			Input:  "192.168.1.1", | ||||||
|  | 			Output: true, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Input:  "192.0.0.0", | ||||||
|  | 			Output: true, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Input:  "192.0.1.0", | ||||||
|  | 			Output: false, | ||||||
|  | 		}, { | ||||||
|  | 			Input:  "0.1.0.0", | ||||||
|  | 			Output: true, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Input:  "1.0.0.1", | ||||||
|  | 			Output: false, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Input:  "8.8.8.7", | ||||||
|  | 			Output: false, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Input:  "8.8.8.8", | ||||||
|  | 			Output: true, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Input:  "2001:cdba::3257:9652", | ||||||
|  | 			Output: false, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Input:  "91.108.255.254", | ||||||
|  | 			Output: true, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, testCase := range testCases { | ||||||
|  | 		ip := net.ParseAddress(testCase.Input).IP() | ||||||
|  | 		actual := matcher.Match(ip) | ||||||
|  | 		if actual != testCase.Output { | ||||||
|  | 			t.Error("expect input", testCase.Input, "to be", testCase.Output, ", but actually", actual) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestGeoIPMatcher4CN(t *testing.T) { | ||||||
|  | 	ips, err := loadGeoIP("CN") | ||||||
|  | 	common.Must(err) | ||||||
|  |  | ||||||
|  | 	matcher := &router.GeoIPMatcher{} | ||||||
|  | 	common.Must(matcher.Init(ips)) | ||||||
|  |  | ||||||
|  | 	if matcher.Match([]byte{8, 8, 8, 8}) { | ||||||
|  | 		t.Error("expect CN geoip doesn't contain 8.8.8.8, but actually does") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestGeoIPMatcher6US(t *testing.T) { | ||||||
|  | 	ips, err := loadGeoIP("US") | ||||||
|  | 	common.Must(err) | ||||||
|  |  | ||||||
|  | 	matcher := &router.GeoIPMatcher{} | ||||||
|  | 	common.Must(matcher.Init(ips)) | ||||||
|  |  | ||||||
|  | 	if !matcher.Match(net.ParseAddress("2001:4860:4860::8888").IP()) { | ||||||
|  | 		t.Error("expect US geoip contain 2001:4860:4860::8888, but actually not") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func loadGeoIP(country string) ([]*router.CIDR, error) { | ||||||
|  | 	geoipBytes, err := filesystem.ReadAsset("geoip.dat") | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	var geoipList router.GeoIPList | ||||||
|  | 	if err := proto.Unmarshal(geoipBytes, &geoipList); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, geoip := range geoipList.Entry { | ||||||
|  | 		if geoip.CountryCode == country { | ||||||
|  | 			return geoip.Cidr, nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	panic("country not found: " + country) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func BenchmarkGeoIPMatcher4CN(b *testing.B) { | ||||||
|  | 	ips, err := loadGeoIP("CN") | ||||||
|  | 	common.Must(err) | ||||||
|  |  | ||||||
|  | 	matcher := &router.GeoIPMatcher{} | ||||||
|  | 	common.Must(matcher.Init(ips)) | ||||||
|  |  | ||||||
|  | 	b.ResetTimer() | ||||||
|  |  | ||||||
|  | 	for i := 0; i < b.N; i++ { | ||||||
|  | 		_ = matcher.Match([]byte{8, 8, 8, 8}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func BenchmarkGeoIPMatcher6US(b *testing.B) { | ||||||
|  | 	ips, err := loadGeoIP("US") | ||||||
|  | 	common.Must(err) | ||||||
|  |  | ||||||
|  | 	matcher := &router.GeoIPMatcher{} | ||||||
|  | 	common.Must(matcher.Init(ips)) | ||||||
|  |  | ||||||
|  | 	b.ResetTimer() | ||||||
|  |  | ||||||
|  | 	for i := 0; i < b.N; i++ { | ||||||
|  | 		_ = matcher.Match(net.ParseAddress("2001:4860:4860::8888").IP()) | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										446
									
								
								app/router/condition_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										446
									
								
								app/router/condition_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,446 @@ | |||||||
|  | package router_test | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"os" | ||||||
|  | 	"path/filepath" | ||||||
|  | 	"strconv" | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"github.com/golang/protobuf/proto" | ||||||
|  |  | ||||||
|  | 	. "github.com/xtls/xray-core/v1/app/router" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/errors" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/net" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/platform" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/platform/filesystem" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/protocol" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/protocol/http" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/session" | ||||||
|  | 	"github.com/xtls/xray-core/v1/features/routing" | ||||||
|  | 	routing_session "github.com/xtls/xray-core/v1/features/routing/session" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func init() { | ||||||
|  | 	wd, err := os.Getwd() | ||||||
|  | 	common.Must(err) | ||||||
|  |  | ||||||
|  | 	if _, err := os.Stat(platform.GetAssetLocation("geoip.dat")); err != nil && os.IsNotExist(err) { | ||||||
|  | 		common.Must(filesystem.CopyFile(platform.GetAssetLocation("geoip.dat"), filepath.Join(wd, "..", "..", "release", "config", "geoip.dat"))) | ||||||
|  | 	} | ||||||
|  | 	if _, err := os.Stat(platform.GetAssetLocation("geosite.dat")); err != nil && os.IsNotExist(err) { | ||||||
|  | 		common.Must(filesystem.CopyFile(platform.GetAssetLocation("geosite.dat"), filepath.Join(wd, "..", "..", "release", "config", "geosite.dat"))) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func withBackground() routing.Context { | ||||||
|  | 	return &routing_session.Context{} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func withOutbound(outbound *session.Outbound) routing.Context { | ||||||
|  | 	return &routing_session.Context{Outbound: outbound} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func withInbound(inbound *session.Inbound) routing.Context { | ||||||
|  | 	return &routing_session.Context{Inbound: inbound} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func withContent(content *session.Content) routing.Context { | ||||||
|  | 	return &routing_session.Context{Content: content} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestRoutingRule(t *testing.T) { | ||||||
|  | 	type ruleTest struct { | ||||||
|  | 		input  routing.Context | ||||||
|  | 		output bool | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	cases := []struct { | ||||||
|  | 		rule *RoutingRule | ||||||
|  | 		test []ruleTest | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			rule: &RoutingRule{ | ||||||
|  | 				Domain: []*Domain{ | ||||||
|  | 					{ | ||||||
|  | 						Value: "example.com", | ||||||
|  | 						Type:  Domain_Plain, | ||||||
|  | 					}, | ||||||
|  | 					{ | ||||||
|  | 						Value: "google.com", | ||||||
|  | 						Type:  Domain_Domain, | ||||||
|  | 					}, | ||||||
|  | 					{ | ||||||
|  | 						Value: "^facebook\\.com$", | ||||||
|  | 						Type:  Domain_Regex, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			test: []ruleTest{ | ||||||
|  | 				{ | ||||||
|  | 					input:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.DomainAddress("example.com"), 80)}), | ||||||
|  | 					output: true, | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					input:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.DomainAddress("www.example.com.www"), 80)}), | ||||||
|  | 					output: true, | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					input:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.DomainAddress("example.co"), 80)}), | ||||||
|  | 					output: false, | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					input:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.DomainAddress("www.google.com"), 80)}), | ||||||
|  | 					output: true, | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					input:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.DomainAddress("facebook.com"), 80)}), | ||||||
|  | 					output: true, | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					input:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.DomainAddress("www.facebook.com"), 80)}), | ||||||
|  | 					output: false, | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					input:  withBackground(), | ||||||
|  | 					output: false, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			rule: &RoutingRule{ | ||||||
|  | 				Cidr: []*CIDR{ | ||||||
|  | 					{ | ||||||
|  | 						Ip:     []byte{8, 8, 8, 8}, | ||||||
|  | 						Prefix: 32, | ||||||
|  | 					}, | ||||||
|  | 					{ | ||||||
|  | 						Ip:     []byte{8, 8, 8, 8}, | ||||||
|  | 						Prefix: 32, | ||||||
|  | 					}, | ||||||
|  | 					{ | ||||||
|  | 						Ip:     net.ParseAddress("2001:0db8:85a3:0000:0000:8a2e:0370:7334").IP(), | ||||||
|  | 						Prefix: 128, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			test: []ruleTest{ | ||||||
|  | 				{ | ||||||
|  | 					input:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.ParseAddress("8.8.8.8"), 80)}), | ||||||
|  | 					output: true, | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					input:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.ParseAddress("8.8.4.4"), 80)}), | ||||||
|  | 					output: false, | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					input:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.ParseAddress("2001:0db8:85a3:0000:0000:8a2e:0370:7334"), 80)}), | ||||||
|  | 					output: true, | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					input:  withBackground(), | ||||||
|  | 					output: false, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			rule: &RoutingRule{ | ||||||
|  | 				Geoip: []*GeoIP{ | ||||||
|  | 					{ | ||||||
|  | 						Cidr: []*CIDR{ | ||||||
|  | 							{ | ||||||
|  | 								Ip:     []byte{8, 8, 8, 8}, | ||||||
|  | 								Prefix: 32, | ||||||
|  | 							}, | ||||||
|  | 							{ | ||||||
|  | 								Ip:     []byte{8, 8, 8, 8}, | ||||||
|  | 								Prefix: 32, | ||||||
|  | 							}, | ||||||
|  | 							{ | ||||||
|  | 								Ip:     net.ParseAddress("2001:0db8:85a3:0000:0000:8a2e:0370:7334").IP(), | ||||||
|  | 								Prefix: 128, | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			test: []ruleTest{ | ||||||
|  | 				{ | ||||||
|  | 					input:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.ParseAddress("8.8.8.8"), 80)}), | ||||||
|  | 					output: true, | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					input:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.ParseAddress("8.8.4.4"), 80)}), | ||||||
|  | 					output: false, | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					input:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.ParseAddress("2001:0db8:85a3:0000:0000:8a2e:0370:7334"), 80)}), | ||||||
|  | 					output: true, | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					input:  withBackground(), | ||||||
|  | 					output: false, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			rule: &RoutingRule{ | ||||||
|  | 				SourceCidr: []*CIDR{ | ||||||
|  | 					{ | ||||||
|  | 						Ip:     []byte{192, 168, 0, 0}, | ||||||
|  | 						Prefix: 16, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			test: []ruleTest{ | ||||||
|  | 				{ | ||||||
|  | 					input:  withInbound(&session.Inbound{Source: net.TCPDestination(net.ParseAddress("192.168.0.1"), 80)}), | ||||||
|  | 					output: true, | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					input:  withInbound(&session.Inbound{Source: net.TCPDestination(net.ParseAddress("10.0.0.1"), 80)}), | ||||||
|  | 					output: false, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			rule: &RoutingRule{ | ||||||
|  | 				UserEmail: []string{ | ||||||
|  | 					"admin@example.com", | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			test: []ruleTest{ | ||||||
|  | 				{ | ||||||
|  | 					input:  withInbound(&session.Inbound{User: &protocol.MemoryUser{Email: "admin@example.com"}}), | ||||||
|  | 					output: true, | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					input:  withInbound(&session.Inbound{User: &protocol.MemoryUser{Email: "love@example.com"}}), | ||||||
|  | 					output: false, | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					input:  withBackground(), | ||||||
|  | 					output: false, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			rule: &RoutingRule{ | ||||||
|  | 				Protocol: []string{"http"}, | ||||||
|  | 			}, | ||||||
|  | 			test: []ruleTest{ | ||||||
|  | 				{ | ||||||
|  | 					input:  withContent(&session.Content{Protocol: (&http.SniffHeader{}).Protocol()}), | ||||||
|  | 					output: true, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			rule: &RoutingRule{ | ||||||
|  | 				InboundTag: []string{"test", "test1"}, | ||||||
|  | 			}, | ||||||
|  | 			test: []ruleTest{ | ||||||
|  | 				{ | ||||||
|  | 					input:  withInbound(&session.Inbound{Tag: "test"}), | ||||||
|  | 					output: true, | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					input:  withInbound(&session.Inbound{Tag: "test2"}), | ||||||
|  | 					output: false, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			rule: &RoutingRule{ | ||||||
|  | 				PortList: &net.PortList{ | ||||||
|  | 					Range: []*net.PortRange{ | ||||||
|  | 						{From: 443, To: 443}, | ||||||
|  | 						{From: 1000, To: 1100}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			test: []ruleTest{ | ||||||
|  | 				{ | ||||||
|  | 					input:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.LocalHostIP, 443)}), | ||||||
|  | 					output: true, | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					input:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.LocalHostIP, 1100)}), | ||||||
|  | 					output: true, | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					input:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.LocalHostIP, 1005)}), | ||||||
|  | 					output: true, | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					input:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.LocalHostIP, 53)}), | ||||||
|  | 					output: false, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			rule: &RoutingRule{ | ||||||
|  | 				SourcePortList: &net.PortList{ | ||||||
|  | 					Range: []*net.PortRange{ | ||||||
|  | 						{From: 123, To: 123}, | ||||||
|  | 						{From: 9993, To: 9999}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			test: []ruleTest{ | ||||||
|  | 				{ | ||||||
|  | 					input:  withInbound(&session.Inbound{Source: net.UDPDestination(net.LocalHostIP, 123)}), | ||||||
|  | 					output: true, | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					input:  withInbound(&session.Inbound{Source: net.UDPDestination(net.LocalHostIP, 9999)}), | ||||||
|  | 					output: true, | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					input:  withInbound(&session.Inbound{Source: net.UDPDestination(net.LocalHostIP, 9994)}), | ||||||
|  | 					output: true, | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					input:  withInbound(&session.Inbound{Source: net.UDPDestination(net.LocalHostIP, 53)}), | ||||||
|  | 					output: false, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			rule: &RoutingRule{ | ||||||
|  | 				Protocol:   []string{"http"}, | ||||||
|  | 				Attributes: "attrs[':path'].startswith('/test')", | ||||||
|  | 			}, | ||||||
|  | 			test: []ruleTest{ | ||||||
|  | 				{ | ||||||
|  | 					input:  withContent(&session.Content{Protocol: "http/1.1", Attributes: map[string]string{":path": "/test/1"}}), | ||||||
|  | 					output: true, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, test := range cases { | ||||||
|  | 		cond, err := test.rule.BuildCondition() | ||||||
|  | 		common.Must(err) | ||||||
|  |  | ||||||
|  | 		for _, subtest := range test.test { | ||||||
|  | 			actual := cond.Apply(subtest.input) | ||||||
|  | 			if actual != subtest.output { | ||||||
|  | 				t.Error("test case failed: ", subtest.input, " expected ", subtest.output, " but got ", actual) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func loadGeoSite(country string) ([]*Domain, error) { | ||||||
|  | 	geositeBytes, err := filesystem.ReadAsset("geosite.dat") | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	var geositeList GeoSiteList | ||||||
|  | 	if err := proto.Unmarshal(geositeBytes, &geositeList); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, site := range geositeList.Entry { | ||||||
|  | 		if site.CountryCode == country { | ||||||
|  | 			return site.Domain, nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil, errors.New("country not found: " + country) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestChinaSites(t *testing.T) { | ||||||
|  | 	domains, err := loadGeoSite("CN") | ||||||
|  | 	common.Must(err) | ||||||
|  |  | ||||||
|  | 	matcher, err := NewDomainMatcher(domains) | ||||||
|  | 	common.Must(err) | ||||||
|  |  | ||||||
|  | 	type TestCase struct { | ||||||
|  | 		Domain string | ||||||
|  | 		Output bool | ||||||
|  | 	} | ||||||
|  | 	testCases := []TestCase{ | ||||||
|  | 		{ | ||||||
|  | 			Domain: "163.com", | ||||||
|  | 			Output: true, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Domain: "163.com", | ||||||
|  | 			Output: true, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Domain: "164.com", | ||||||
|  | 			Output: false, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Domain: "164.com", | ||||||
|  | 			Output: false, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for i := 0; i < 1024; i++ { | ||||||
|  | 		testCases = append(testCases, TestCase{Domain: strconv.Itoa(i) + ".not-exists.com", Output: false}) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, testCase := range testCases { | ||||||
|  | 		r := matcher.ApplyDomain(testCase.Domain) | ||||||
|  | 		if r != testCase.Output { | ||||||
|  | 			t.Error("expected output ", testCase.Output, " for domain ", testCase.Domain, " but got ", r) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func BenchmarkMultiGeoIPMatcher(b *testing.B) { | ||||||
|  | 	var geoips []*GeoIP | ||||||
|  |  | ||||||
|  | 	{ | ||||||
|  | 		ips, err := loadGeoIP("CN") | ||||||
|  | 		common.Must(err) | ||||||
|  | 		geoips = append(geoips, &GeoIP{ | ||||||
|  | 			CountryCode: "CN", | ||||||
|  | 			Cidr:        ips, | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	{ | ||||||
|  | 		ips, err := loadGeoIP("JP") | ||||||
|  | 		common.Must(err) | ||||||
|  | 		geoips = append(geoips, &GeoIP{ | ||||||
|  | 			CountryCode: "JP", | ||||||
|  | 			Cidr:        ips, | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	{ | ||||||
|  | 		ips, err := loadGeoIP("CA") | ||||||
|  | 		common.Must(err) | ||||||
|  | 		geoips = append(geoips, &GeoIP{ | ||||||
|  | 			CountryCode: "CA", | ||||||
|  | 			Cidr:        ips, | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	{ | ||||||
|  | 		ips, err := loadGeoIP("US") | ||||||
|  | 		common.Must(err) | ||||||
|  | 		geoips = append(geoips, &GeoIP{ | ||||||
|  | 			CountryCode: "US", | ||||||
|  | 			Cidr:        ips, | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	matcher, err := NewMultiGeoIPMatcher(geoips, false) | ||||||
|  | 	common.Must(err) | ||||||
|  |  | ||||||
|  | 	ctx := withOutbound(&session.Outbound{Target: net.TCPDestination(net.ParseAddress("8.8.8.8"), 80)}) | ||||||
|  |  | ||||||
|  | 	b.ResetTimer() | ||||||
|  |  | ||||||
|  | 	for i := 0; i < b.N; i++ { | ||||||
|  | 		_ = matcher.Apply(ctx) | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										156
									
								
								app/router/config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								app/router/config.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,156 @@ | |||||||
|  | // +build !confonly | ||||||
|  |  | ||||||
|  | package router | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/net" | ||||||
|  | 	"github.com/xtls/xray-core/v1/features/outbound" | ||||||
|  | 	"github.com/xtls/xray-core/v1/features/routing" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // CIDRList is an alias of []*CIDR to provide sort.Interface. | ||||||
|  | type CIDRList []*CIDR | ||||||
|  |  | ||||||
|  | // Len implements sort.Interface. | ||||||
|  | func (l *CIDRList) Len() int { | ||||||
|  | 	return len(*l) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Less implements sort.Interface. | ||||||
|  | func (l *CIDRList) Less(i int, j int) bool { | ||||||
|  | 	ci := (*l)[i] | ||||||
|  | 	cj := (*l)[j] | ||||||
|  |  | ||||||
|  | 	if len(ci.Ip) < len(cj.Ip) { | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(ci.Ip) > len(cj.Ip) { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for k := 0; k < len(ci.Ip); k++ { | ||||||
|  | 		if ci.Ip[k] < cj.Ip[k] { | ||||||
|  | 			return true | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if ci.Ip[k] > cj.Ip[k] { | ||||||
|  | 			return false | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return ci.Prefix < cj.Prefix | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Swap implements sort.Interface. | ||||||
|  | func (l *CIDRList) Swap(i int, j int) { | ||||||
|  | 	(*l)[i], (*l)[j] = (*l)[j], (*l)[i] | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Rule struct { | ||||||
|  | 	Tag       string | ||||||
|  | 	Balancer  *Balancer | ||||||
|  | 	Condition Condition | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r *Rule) GetTag() (string, error) { | ||||||
|  | 	if r.Balancer != nil { | ||||||
|  | 		return r.Balancer.PickOutbound() | ||||||
|  | 	} | ||||||
|  | 	return r.Tag, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Apply checks rule matching of current routing context. | ||||||
|  | func (r *Rule) Apply(ctx routing.Context) bool { | ||||||
|  | 	return r.Condition.Apply(ctx) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (rr *RoutingRule) BuildCondition() (Condition, error) { | ||||||
|  | 	conds := NewConditionChan() | ||||||
|  |  | ||||||
|  | 	if len(rr.Domain) > 0 { | ||||||
|  | 		matcher, err := NewDomainMatcher(rr.Domain) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, newError("failed to build domain condition").Base(err) | ||||||
|  | 		} | ||||||
|  | 		conds.Add(matcher) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(rr.UserEmail) > 0 { | ||||||
|  | 		conds.Add(NewUserMatcher(rr.UserEmail)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(rr.InboundTag) > 0 { | ||||||
|  | 		conds.Add(NewInboundTagMatcher(rr.InboundTag)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if rr.PortList != nil { | ||||||
|  | 		conds.Add(NewPortMatcher(rr.PortList, false)) | ||||||
|  | 	} else if rr.PortRange != nil { | ||||||
|  | 		conds.Add(NewPortMatcher(&net.PortList{Range: []*net.PortRange{rr.PortRange}}, false)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if rr.SourcePortList != nil { | ||||||
|  | 		conds.Add(NewPortMatcher(rr.SourcePortList, true)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(rr.Networks) > 0 { | ||||||
|  | 		conds.Add(NewNetworkMatcher(rr.Networks)) | ||||||
|  | 	} else if rr.NetworkList != nil { | ||||||
|  | 		conds.Add(NewNetworkMatcher(rr.NetworkList.Network)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(rr.Geoip) > 0 { | ||||||
|  | 		cond, err := NewMultiGeoIPMatcher(rr.Geoip, false) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		conds.Add(cond) | ||||||
|  | 	} else if len(rr.Cidr) > 0 { | ||||||
|  | 		cond, err := NewMultiGeoIPMatcher([]*GeoIP{{Cidr: rr.Cidr}}, false) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		conds.Add(cond) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(rr.SourceGeoip) > 0 { | ||||||
|  | 		cond, err := NewMultiGeoIPMatcher(rr.SourceGeoip, true) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		conds.Add(cond) | ||||||
|  | 	} else if len(rr.SourceCidr) > 0 { | ||||||
|  | 		cond, err := NewMultiGeoIPMatcher([]*GeoIP{{Cidr: rr.SourceCidr}}, true) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		conds.Add(cond) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(rr.Protocol) > 0 { | ||||||
|  | 		conds.Add(NewProtocolMatcher(rr.Protocol)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(rr.Attributes) > 0 { | ||||||
|  | 		cond, err := NewAttributeMatcher(rr.Attributes) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		conds.Add(cond) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if conds.Len() == 0 { | ||||||
|  | 		return nil, newError("this rule has no effective fields").AtWarning() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return conds, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (br *BalancingRule) Build(ohm outbound.Manager) (*Balancer, error) { | ||||||
|  | 	return &Balancer{ | ||||||
|  | 		selectors: br.OutboundSelector, | ||||||
|  | 		strategy:  &RandomStrategy{}, | ||||||
|  | 		ohm:       ohm, | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
							
								
								
									
										1242
									
								
								app/router/config.pb.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1242
									
								
								app/router/config.pb.go
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										146
									
								
								app/router/config.proto
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								app/router/config.proto
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,146 @@ | |||||||
|  | syntax = "proto3"; | ||||||
|  |  | ||||||
|  | package xray.app.router; | ||||||
|  | option csharp_namespace = "Xray.App.Router"; | ||||||
|  | option go_package = "github.com/xtls/xray-core/v1/app/router"; | ||||||
|  | option java_package = "com.xray.app.router"; | ||||||
|  | option java_multiple_files = true; | ||||||
|  |  | ||||||
|  | import "common/net/port.proto"; | ||||||
|  | import "common/net/network.proto"; | ||||||
|  |  | ||||||
|  | // Domain for routing decision. | ||||||
|  | message Domain { | ||||||
|  |   // Type of domain value. | ||||||
|  |   enum Type { | ||||||
|  |     // The value is used as is. | ||||||
|  |     Plain = 0; | ||||||
|  |     // The value is used as a regular expression. | ||||||
|  |     Regex = 1; | ||||||
|  |     // The value is a root domain. | ||||||
|  |     Domain = 2; | ||||||
|  |     // The value is a domain. | ||||||
|  |     Full = 3; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Domain matching type. | ||||||
|  |   Type type = 1; | ||||||
|  |  | ||||||
|  |   // Domain value. | ||||||
|  |   string value = 2; | ||||||
|  |  | ||||||
|  |   message Attribute { | ||||||
|  |     string key = 1; | ||||||
|  |  | ||||||
|  |     oneof typed_value { | ||||||
|  |       bool bool_value = 2; | ||||||
|  |       int64 int_value = 3; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Attributes of this domain. May be used for filtering. | ||||||
|  |   repeated Attribute attribute = 3; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // IP for routing decision, in CIDR form. | ||||||
|  | message CIDR { | ||||||
|  |   // IP address, should be either 4 or 16 bytes. | ||||||
|  |   bytes ip = 1; | ||||||
|  |  | ||||||
|  |   // Number of leading ones in the network mask. | ||||||
|  |   uint32 prefix = 2; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message GeoIP { | ||||||
|  |   string country_code = 1; | ||||||
|  |   repeated CIDR cidr = 2; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message GeoIPList { | ||||||
|  |   repeated GeoIP entry = 1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message GeoSite { | ||||||
|  |   string country_code = 1; | ||||||
|  |   repeated Domain domain = 2; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message GeoSiteList { | ||||||
|  |   repeated GeoSite entry = 1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message RoutingRule { | ||||||
|  |   oneof target_tag { | ||||||
|  |     // Tag of outbound that this rule is pointing to. | ||||||
|  |     string tag = 1; | ||||||
|  |  | ||||||
|  |     // Tag of routing balancer. | ||||||
|  |     string balancing_tag = 12; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // List of domains for target domain matching. | ||||||
|  |   repeated Domain domain = 2; | ||||||
|  |  | ||||||
|  |   // List of CIDRs for target IP address matching. | ||||||
|  |   // Deprecated. Use geoip below. | ||||||
|  |   repeated CIDR cidr = 3 [deprecated = true]; | ||||||
|  |  | ||||||
|  |   // List of GeoIPs for target IP address matching. If this entry exists, the | ||||||
|  |   // cidr above will have no effect. GeoIP fields with the same country code are | ||||||
|  |   // supposed to contain exactly same content. They will be merged during | ||||||
|  |   // runtime. For customized GeoIPs, please leave country code empty. | ||||||
|  |   repeated GeoIP geoip = 10; | ||||||
|  |  | ||||||
|  |   // A range of port [from, to]. If the destination port is in this range, this | ||||||
|  |   // rule takes effect. Deprecated. Use port_list. | ||||||
|  |   xray.common.net.PortRange port_range = 4 [deprecated = true]; | ||||||
|  |  | ||||||
|  |   // List of ports. | ||||||
|  |   xray.common.net.PortList port_list = 14; | ||||||
|  |  | ||||||
|  |   // List of networks. Deprecated. Use networks. | ||||||
|  |   xray.common.net.NetworkList network_list = 5 [deprecated = true]; | ||||||
|  |  | ||||||
|  |   // List of networks for matching. | ||||||
|  |   repeated xray.common.net.Network networks = 13; | ||||||
|  |  | ||||||
|  |   // List of CIDRs for source IP address matching. | ||||||
|  |   repeated CIDR source_cidr = 6 [deprecated = true]; | ||||||
|  |  | ||||||
|  |   // List of GeoIPs for source IP address matching. If this entry exists, the | ||||||
|  |   // source_cidr above will have no effect. | ||||||
|  |   repeated GeoIP source_geoip = 11; | ||||||
|  |  | ||||||
|  |   // List of ports for source port matching. | ||||||
|  |   xray.common.net.PortList source_port_list = 16; | ||||||
|  |  | ||||||
|  |   repeated string user_email = 7; | ||||||
|  |   repeated string inbound_tag = 8; | ||||||
|  |   repeated string protocol = 9; | ||||||
|  |  | ||||||
|  |   string attributes = 15; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message BalancingRule { | ||||||
|  |   string tag = 1; | ||||||
|  |   repeated string outbound_selector = 2; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message Config { | ||||||
|  |   enum DomainStrategy { | ||||||
|  |     // Use domain as is. | ||||||
|  |     AsIs = 0; | ||||||
|  |  | ||||||
|  |     // Always resolve IP for domains. | ||||||
|  |     UseIp = 1; | ||||||
|  |  | ||||||
|  |     // Resolve to IP if the domain doesn't match any rules. | ||||||
|  |     IpIfNonMatch = 2; | ||||||
|  |  | ||||||
|  |     // Resolve to IP if any rule requires IP matching. | ||||||
|  |     IpOnDemand = 3; | ||||||
|  |   } | ||||||
|  |   DomainStrategy domain_strategy = 1; | ||||||
|  |   repeated RoutingRule rule = 2; | ||||||
|  |   repeated BalancingRule balancing_rule = 3; | ||||||
|  | } | ||||||
							
								
								
									
										9
									
								
								app/router/errors.generated.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								app/router/errors.generated.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | package router | ||||||
|  |  | ||||||
|  | import "github.com/xtls/xray-core/v1/common/errors" | ||||||
|  |  | ||||||
|  | type errPathObjHolder struct{} | ||||||
|  |  | ||||||
|  | func newError(values ...interface{}) *errors.Error { | ||||||
|  | 	return errors.New(values...).WithPathObj(errPathObjHolder{}) | ||||||
|  | } | ||||||
							
								
								
									
										146
									
								
								app/router/router.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								app/router/router.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,146 @@ | |||||||
|  | // +build !confonly | ||||||
|  |  | ||||||
|  | package router | ||||||
|  |  | ||||||
|  | //go:generate go run github.com/xtls/xray-core/v1/common/errors/errorgen | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  |  | ||||||
|  | 	"github.com/xtls/xray-core/v1/common" | ||||||
|  | 	"github.com/xtls/xray-core/v1/core" | ||||||
|  | 	"github.com/xtls/xray-core/v1/features/dns" | ||||||
|  | 	"github.com/xtls/xray-core/v1/features/outbound" | ||||||
|  | 	"github.com/xtls/xray-core/v1/features/routing" | ||||||
|  | 	routing_dns "github.com/xtls/xray-core/v1/features/routing/dns" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Router is an implementation of routing.Router. | ||||||
|  | type Router struct { | ||||||
|  | 	domainStrategy Config_DomainStrategy | ||||||
|  | 	rules          []*Rule | ||||||
|  | 	balancers      map[string]*Balancer | ||||||
|  | 	dns            dns.Client | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Route is an implementation of routing.Route. | ||||||
|  | type Route struct { | ||||||
|  | 	routing.Context | ||||||
|  | 	outboundGroupTags []string | ||||||
|  | 	outboundTag       string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Init initializes the Router. | ||||||
|  | func (r *Router) Init(config *Config, d dns.Client, ohm outbound.Manager) error { | ||||||
|  | 	r.domainStrategy = config.DomainStrategy | ||||||
|  | 	r.dns = d | ||||||
|  |  | ||||||
|  | 	r.balancers = make(map[string]*Balancer, len(config.BalancingRule)) | ||||||
|  | 	for _, rule := range config.BalancingRule { | ||||||
|  | 		balancer, err := rule.Build(ohm) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		r.balancers[rule.Tag] = balancer | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	r.rules = make([]*Rule, 0, len(config.Rule)) | ||||||
|  | 	for _, rule := range config.Rule { | ||||||
|  | 		cond, err := rule.BuildCondition() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		rr := &Rule{ | ||||||
|  | 			Condition: cond, | ||||||
|  | 			Tag:       rule.GetTag(), | ||||||
|  | 		} | ||||||
|  | 		btag := rule.GetBalancingTag() | ||||||
|  | 		if len(btag) > 0 { | ||||||
|  | 			brule, found := r.balancers[btag] | ||||||
|  | 			if !found { | ||||||
|  | 				return newError("balancer ", btag, " not found") | ||||||
|  | 			} | ||||||
|  | 			rr.Balancer = brule | ||||||
|  | 		} | ||||||
|  | 		r.rules = append(r.rules, rr) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // PickRoute implements routing.Router. | ||||||
|  | func (r *Router) PickRoute(ctx routing.Context) (routing.Route, error) { | ||||||
|  | 	rule, ctx, err := r.pickRouteInternal(ctx) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	tag, err := rule.GetTag() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return &Route{Context: ctx, outboundTag: tag}, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r *Router) pickRouteInternal(ctx routing.Context) (*Rule, routing.Context, error) { | ||||||
|  | 	if r.domainStrategy == Config_IpOnDemand { | ||||||
|  | 		ctx = routing_dns.ContextWithDNSClient(ctx, r.dns) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, rule := range r.rules { | ||||||
|  | 		if rule.Apply(ctx) { | ||||||
|  | 			return rule, ctx, nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if r.domainStrategy != Config_IpIfNonMatch || len(ctx.GetTargetDomain()) == 0 { | ||||||
|  | 		return nil, ctx, common.ErrNoClue | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ctx = routing_dns.ContextWithDNSClient(ctx, r.dns) | ||||||
|  |  | ||||||
|  | 	// Try applying rules again if we have IPs. | ||||||
|  | 	for _, rule := range r.rules { | ||||||
|  | 		if rule.Apply(ctx) { | ||||||
|  | 			return rule, ctx, nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil, ctx, common.ErrNoClue | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Start implements common.Runnable. | ||||||
|  | func (*Router) Start() error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Close implements common.Closable. | ||||||
|  | func (*Router) Close() error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Type implement common.HasType. | ||||||
|  | func (*Router) Type() interface{} { | ||||||
|  | 	return routing.RouterType() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // GetOutboundGroupTags implements routing.Route. | ||||||
|  | func (r *Route) GetOutboundGroupTags() []string { | ||||||
|  | 	return r.outboundGroupTags | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // GetOutboundTag implements routing.Route. | ||||||
|  | func (r *Route) GetOutboundTag() string { | ||||||
|  | 	return r.outboundTag | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func init() { | ||||||
|  | 	common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { | ||||||
|  | 		r := new(Router) | ||||||
|  | 		if err := core.RequireFeatures(ctx, func(d dns.Client, ohm outbound.Manager) error { | ||||||
|  | 			return r.Init(config.(*Config), d, ohm) | ||||||
|  | 		}); err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		return r, nil | ||||||
|  | 	})) | ||||||
|  | } | ||||||
							
								
								
									
										198
									
								
								app/router/router_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										198
									
								
								app/router/router_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,198 @@ | |||||||
|  | package router_test | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"github.com/golang/mock/gomock" | ||||||
|  | 	. "github.com/xtls/xray-core/v1/app/router" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/net" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/session" | ||||||
|  | 	"github.com/xtls/xray-core/v1/features/outbound" | ||||||
|  | 	routing_session "github.com/xtls/xray-core/v1/features/routing/session" | ||||||
|  | 	"github.com/xtls/xray-core/v1/testing/mocks" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type mockOutboundManager struct { | ||||||
|  | 	outbound.Manager | ||||||
|  | 	outbound.HandlerSelector | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestSimpleRouter(t *testing.T) { | ||||||
|  | 	config := &Config{ | ||||||
|  | 		Rule: []*RoutingRule{ | ||||||
|  | 			{ | ||||||
|  | 				TargetTag: &RoutingRule_Tag{ | ||||||
|  | 					Tag: "test", | ||||||
|  | 				}, | ||||||
|  | 				Networks: []net.Network{net.Network_TCP}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	mockCtl := gomock.NewController(t) | ||||||
|  | 	defer mockCtl.Finish() | ||||||
|  |  | ||||||
|  | 	mockDNS := mocks.NewDNSClient(mockCtl) | ||||||
|  | 	mockOhm := mocks.NewOutboundManager(mockCtl) | ||||||
|  | 	mockHs := mocks.NewOutboundHandlerSelector(mockCtl) | ||||||
|  |  | ||||||
|  | 	r := new(Router) | ||||||
|  | 	common.Must(r.Init(config, mockDNS, &mockOutboundManager{ | ||||||
|  | 		Manager:         mockOhm, | ||||||
|  | 		HandlerSelector: mockHs, | ||||||
|  | 	})) | ||||||
|  |  | ||||||
|  | 	ctx := session.ContextWithOutbound(context.Background(), &session.Outbound{Target: net.TCPDestination(net.DomainAddress("example.com"), 80)}) | ||||||
|  | 	route, err := r.PickRoute(routing_session.AsRoutingContext(ctx)) | ||||||
|  | 	common.Must(err) | ||||||
|  | 	if tag := route.GetOutboundTag(); tag != "test" { | ||||||
|  | 		t.Error("expect tag 'test', bug actually ", tag) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestSimpleBalancer(t *testing.T) { | ||||||
|  | 	config := &Config{ | ||||||
|  | 		Rule: []*RoutingRule{ | ||||||
|  | 			{ | ||||||
|  | 				TargetTag: &RoutingRule_BalancingTag{ | ||||||
|  | 					BalancingTag: "balance", | ||||||
|  | 				}, | ||||||
|  | 				Networks: []net.Network{net.Network_TCP}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		BalancingRule: []*BalancingRule{ | ||||||
|  | 			{ | ||||||
|  | 				Tag:              "balance", | ||||||
|  | 				OutboundSelector: []string{"test-"}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	mockCtl := gomock.NewController(t) | ||||||
|  | 	defer mockCtl.Finish() | ||||||
|  |  | ||||||
|  | 	mockDNS := mocks.NewDNSClient(mockCtl) | ||||||
|  | 	mockOhm := mocks.NewOutboundManager(mockCtl) | ||||||
|  | 	mockHs := mocks.NewOutboundHandlerSelector(mockCtl) | ||||||
|  |  | ||||||
|  | 	mockHs.EXPECT().Select(gomock.Eq([]string{"test-"})).Return([]string{"test"}) | ||||||
|  |  | ||||||
|  | 	r := new(Router) | ||||||
|  | 	common.Must(r.Init(config, mockDNS, &mockOutboundManager{ | ||||||
|  | 		Manager:         mockOhm, | ||||||
|  | 		HandlerSelector: mockHs, | ||||||
|  | 	})) | ||||||
|  |  | ||||||
|  | 	ctx := session.ContextWithOutbound(context.Background(), &session.Outbound{Target: net.TCPDestination(net.DomainAddress("example.com"), 80)}) | ||||||
|  | 	route, err := r.PickRoute(routing_session.AsRoutingContext(ctx)) | ||||||
|  | 	common.Must(err) | ||||||
|  | 	if tag := route.GetOutboundTag(); tag != "test" { | ||||||
|  | 		t.Error("expect tag 'test', bug actually ", tag) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestIPOnDemand(t *testing.T) { | ||||||
|  | 	config := &Config{ | ||||||
|  | 		DomainStrategy: Config_IpOnDemand, | ||||||
|  | 		Rule: []*RoutingRule{ | ||||||
|  | 			{ | ||||||
|  | 				TargetTag: &RoutingRule_Tag{ | ||||||
|  | 					Tag: "test", | ||||||
|  | 				}, | ||||||
|  | 				Cidr: []*CIDR{ | ||||||
|  | 					{ | ||||||
|  | 						Ip:     []byte{192, 168, 0, 0}, | ||||||
|  | 						Prefix: 16, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	mockCtl := gomock.NewController(t) | ||||||
|  | 	defer mockCtl.Finish() | ||||||
|  |  | ||||||
|  | 	mockDNS := mocks.NewDNSClient(mockCtl) | ||||||
|  | 	mockDNS.EXPECT().LookupIP(gomock.Eq("example.com")).Return([]net.IP{{192, 168, 0, 1}}, nil).AnyTimes() | ||||||
|  |  | ||||||
|  | 	r := new(Router) | ||||||
|  | 	common.Must(r.Init(config, mockDNS, nil)) | ||||||
|  |  | ||||||
|  | 	ctx := session.ContextWithOutbound(context.Background(), &session.Outbound{Target: net.TCPDestination(net.DomainAddress("example.com"), 80)}) | ||||||
|  | 	route, err := r.PickRoute(routing_session.AsRoutingContext(ctx)) | ||||||
|  | 	common.Must(err) | ||||||
|  | 	if tag := route.GetOutboundTag(); tag != "test" { | ||||||
|  | 		t.Error("expect tag 'test', bug actually ", tag) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestIPIfNonMatchDomain(t *testing.T) { | ||||||
|  | 	config := &Config{ | ||||||
|  | 		DomainStrategy: Config_IpIfNonMatch, | ||||||
|  | 		Rule: []*RoutingRule{ | ||||||
|  | 			{ | ||||||
|  | 				TargetTag: &RoutingRule_Tag{ | ||||||
|  | 					Tag: "test", | ||||||
|  | 				}, | ||||||
|  | 				Cidr: []*CIDR{ | ||||||
|  | 					{ | ||||||
|  | 						Ip:     []byte{192, 168, 0, 0}, | ||||||
|  | 						Prefix: 16, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	mockCtl := gomock.NewController(t) | ||||||
|  | 	defer mockCtl.Finish() | ||||||
|  |  | ||||||
|  | 	mockDNS := mocks.NewDNSClient(mockCtl) | ||||||
|  | 	mockDNS.EXPECT().LookupIP(gomock.Eq("example.com")).Return([]net.IP{{192, 168, 0, 1}}, nil).AnyTimes() | ||||||
|  |  | ||||||
|  | 	r := new(Router) | ||||||
|  | 	common.Must(r.Init(config, mockDNS, nil)) | ||||||
|  |  | ||||||
|  | 	ctx := session.ContextWithOutbound(context.Background(), &session.Outbound{Target: net.TCPDestination(net.DomainAddress("example.com"), 80)}) | ||||||
|  | 	route, err := r.PickRoute(routing_session.AsRoutingContext(ctx)) | ||||||
|  | 	common.Must(err) | ||||||
|  | 	if tag := route.GetOutboundTag(); tag != "test" { | ||||||
|  | 		t.Error("expect tag 'test', bug actually ", tag) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestIPIfNonMatchIP(t *testing.T) { | ||||||
|  | 	config := &Config{ | ||||||
|  | 		DomainStrategy: Config_IpIfNonMatch, | ||||||
|  | 		Rule: []*RoutingRule{ | ||||||
|  | 			{ | ||||||
|  | 				TargetTag: &RoutingRule_Tag{ | ||||||
|  | 					Tag: "test", | ||||||
|  | 				}, | ||||||
|  | 				Cidr: []*CIDR{ | ||||||
|  | 					{ | ||||||
|  | 						Ip:     []byte{127, 0, 0, 0}, | ||||||
|  | 						Prefix: 8, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	mockCtl := gomock.NewController(t) | ||||||
|  | 	defer mockCtl.Finish() | ||||||
|  |  | ||||||
|  | 	mockDNS := mocks.NewDNSClient(mockCtl) | ||||||
|  |  | ||||||
|  | 	r := new(Router) | ||||||
|  | 	common.Must(r.Init(config, mockDNS, nil)) | ||||||
|  |  | ||||||
|  | 	ctx := session.ContextWithOutbound(context.Background(), &session.Outbound{Target: net.TCPDestination(net.LocalHostIP, 80)}) | ||||||
|  | 	route, err := r.PickRoute(routing_session.AsRoutingContext(ctx)) | ||||||
|  | 	common.Must(err) | ||||||
|  | 	if tag := route.GetOutboundTag(); tag != "test" { | ||||||
|  | 		t.Error("expect tag 'test', bug actually ", tag) | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										174
									
								
								app/stats/channel.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								app/stats/channel.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,174 @@ | |||||||
|  | // +build !confonly | ||||||
|  |  | ||||||
|  | package stats | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"sync" | ||||||
|  |  | ||||||
|  | 	"github.com/xtls/xray-core/v1/common" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Channel is an implementation of stats.Channel. | ||||||
|  | type Channel struct { | ||||||
|  | 	channel     chan channelMessage | ||||||
|  | 	subscribers []chan interface{} | ||||||
|  |  | ||||||
|  | 	// Synchronization components | ||||||
|  | 	access sync.RWMutex | ||||||
|  | 	closed chan struct{} | ||||||
|  |  | ||||||
|  | 	// Channel options | ||||||
|  | 	blocking   bool // Set blocking state if channel buffer reaches limit | ||||||
|  | 	bufferSize int  // Set to 0 as no buffering | ||||||
|  | 	subsLimit  int  // Set to 0 as no subscriber limit | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // NewChannel creates an instance of Statistics Channel. | ||||||
|  | func NewChannel(config *ChannelConfig) *Channel { | ||||||
|  | 	return &Channel{ | ||||||
|  | 		channel:    make(chan channelMessage, config.BufferSize), | ||||||
|  | 		subsLimit:  int(config.SubscriberLimit), | ||||||
|  | 		bufferSize: int(config.BufferSize), | ||||||
|  | 		blocking:   config.Blocking, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Subscribers implements stats.Channel. | ||||||
|  | func (c *Channel) Subscribers() []chan interface{} { | ||||||
|  | 	c.access.RLock() | ||||||
|  | 	defer c.access.RUnlock() | ||||||
|  | 	return c.subscribers | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Subscribe implements stats.Channel. | ||||||
|  | func (c *Channel) Subscribe() (chan interface{}, error) { | ||||||
|  | 	c.access.Lock() | ||||||
|  | 	defer c.access.Unlock() | ||||||
|  | 	if c.subsLimit > 0 && len(c.subscribers) >= c.subsLimit { | ||||||
|  | 		return nil, newError("Number of subscribers has reached limit") | ||||||
|  | 	} | ||||||
|  | 	subscriber := make(chan interface{}, c.bufferSize) | ||||||
|  | 	c.subscribers = append(c.subscribers, subscriber) | ||||||
|  | 	return subscriber, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Unsubscribe implements stats.Channel. | ||||||
|  | func (c *Channel) Unsubscribe(subscriber chan interface{}) error { | ||||||
|  | 	c.access.Lock() | ||||||
|  | 	defer c.access.Unlock() | ||||||
|  | 	for i, s := range c.subscribers { | ||||||
|  | 		if s == subscriber { | ||||||
|  | 			// Copy to new memory block to prevent modifying original data | ||||||
|  | 			subscribers := make([]chan interface{}, len(c.subscribers)-1) | ||||||
|  | 			copy(subscribers[:i], c.subscribers[:i]) | ||||||
|  | 			copy(subscribers[i:], c.subscribers[i+1:]) | ||||||
|  | 			c.subscribers = subscribers | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Publish implements stats.Channel. | ||||||
|  | func (c *Channel) Publish(ctx context.Context, msg interface{}) { | ||||||
|  | 	select { // Early exit if channel closed | ||||||
|  | 	case <-c.closed: | ||||||
|  | 		return | ||||||
|  | 	default: | ||||||
|  | 		pub := channelMessage{context: ctx, message: msg} | ||||||
|  | 		if c.blocking { | ||||||
|  | 			pub.publish(c.channel) | ||||||
|  | 		} else { | ||||||
|  | 			pub.publishNonBlocking(c.channel) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Running returns whether the channel is running. | ||||||
|  | func (c *Channel) Running() bool { | ||||||
|  | 	select { | ||||||
|  | 	case <-c.closed: // Channel closed | ||||||
|  | 	default: // Channel running or not initialized | ||||||
|  | 		if c.closed != nil { // Channel initialized | ||||||
|  | 			return true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Start implements common.Runnable. | ||||||
|  | func (c *Channel) Start() error { | ||||||
|  | 	c.access.Lock() | ||||||
|  | 	defer c.access.Unlock() | ||||||
|  | 	if !c.Running() { | ||||||
|  | 		c.closed = make(chan struct{}) // Reset close signal | ||||||
|  | 		go func() { | ||||||
|  | 			for { | ||||||
|  | 				select { | ||||||
|  | 				case pub := <-c.channel: // Published message received | ||||||
|  | 					for _, sub := range c.Subscribers() { // Concurrency-safe subscribers retrievement | ||||||
|  | 						if c.blocking { | ||||||
|  | 							pub.broadcast(sub) | ||||||
|  | 						} else { | ||||||
|  | 							pub.broadcastNonBlocking(sub) | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 				case <-c.closed: // Channel closed | ||||||
|  | 					for _, sub := range c.Subscribers() { // Remove all subscribers | ||||||
|  | 						common.Must(c.Unsubscribe(sub)) | ||||||
|  | 						close(sub) | ||||||
|  | 					} | ||||||
|  | 					return | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		}() | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Close implements common.Closable. | ||||||
|  | func (c *Channel) Close() error { | ||||||
|  | 	c.access.Lock() | ||||||
|  | 	defer c.access.Unlock() | ||||||
|  | 	if c.Running() { | ||||||
|  | 		close(c.closed) // Send closed signal | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // channelMessage is the published message with guaranteed delivery. | ||||||
|  | // message is discarded only when the context is early cancelled. | ||||||
|  | type channelMessage struct { | ||||||
|  | 	context context.Context | ||||||
|  | 	message interface{} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c channelMessage) publish(publisher chan channelMessage) { | ||||||
|  | 	select { | ||||||
|  | 	case publisher <- c: | ||||||
|  | 	case <-c.context.Done(): | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c channelMessage) publishNonBlocking(publisher chan channelMessage) { | ||||||
|  | 	select { | ||||||
|  | 	case publisher <- c: | ||||||
|  | 	default: // Create another goroutine to keep sending message | ||||||
|  | 		go c.publish(publisher) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c channelMessage) broadcast(subscriber chan interface{}) { | ||||||
|  | 	select { | ||||||
|  | 	case subscriber <- c.message: | ||||||
|  | 	case <-c.context.Done(): | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c channelMessage) broadcastNonBlocking(subscriber chan interface{}) { | ||||||
|  | 	select { | ||||||
|  | 	case subscriber <- c.message: | ||||||
|  | 	default: // Create another goroutine to keep sending message | ||||||
|  | 		go c.broadcast(subscriber) | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										405
									
								
								app/stats/channel_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										405
									
								
								app/stats/channel_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,405 @@ | |||||||
|  | package stats_test | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"fmt" | ||||||
|  | 	"testing" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	. "github.com/xtls/xray-core/v1/app/stats" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common" | ||||||
|  | 	"github.com/xtls/xray-core/v1/features/stats" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestStatsChannel(t *testing.T) { | ||||||
|  | 	// At most 2 subscribers could be registered | ||||||
|  | 	c := NewChannel(&ChannelConfig{SubscriberLimit: 2, Blocking: true}) | ||||||
|  |  | ||||||
|  | 	a, err := stats.SubscribeRunnableChannel(c) | ||||||
|  | 	common.Must(err) | ||||||
|  | 	if !c.Running() { | ||||||
|  | 		t.Fatal("unexpected failure in running channel after first subscription") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	b, err := c.Subscribe() | ||||||
|  | 	common.Must(err) | ||||||
|  |  | ||||||
|  | 	// Test that third subscriber is forbidden | ||||||
|  | 	_, err = c.Subscribe() | ||||||
|  | 	if err == nil { | ||||||
|  | 		t.Fatal("unexpected successful subscription") | ||||||
|  | 	} | ||||||
|  | 	t.Log("expected error: ", err) | ||||||
|  |  | ||||||
|  | 	stopCh := make(chan struct{}) | ||||||
|  | 	errCh := make(chan string) | ||||||
|  |  | ||||||
|  | 	go func() { | ||||||
|  | 		c.Publish(context.Background(), 1) | ||||||
|  | 		c.Publish(context.Background(), 2) | ||||||
|  | 		c.Publish(context.Background(), "3") | ||||||
|  | 		c.Publish(context.Background(), []int{4}) | ||||||
|  | 		stopCh <- struct{}{} | ||||||
|  | 	}() | ||||||
|  |  | ||||||
|  | 	go func() { | ||||||
|  | 		if v, ok := (<-a).(int); !ok || v != 1 { | ||||||
|  | 			errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", 1) | ||||||
|  | 		} | ||||||
|  | 		if v, ok := (<-a).(int); !ok || v != 2 { | ||||||
|  | 			errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", 2) | ||||||
|  | 		} | ||||||
|  | 		if v, ok := (<-a).(string); !ok || v != "3" { | ||||||
|  | 			errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", "3") | ||||||
|  | 		} | ||||||
|  | 		if v, ok := (<-a).([]int); !ok || v[0] != 4 { | ||||||
|  | 			errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", []int{4}) | ||||||
|  | 		} | ||||||
|  | 		stopCh <- struct{}{} | ||||||
|  | 	}() | ||||||
|  |  | ||||||
|  | 	go func() { | ||||||
|  | 		if v, ok := (<-b).(int); !ok || v != 1 { | ||||||
|  | 			errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", 1) | ||||||
|  | 		} | ||||||
|  | 		if v, ok := (<-b).(int); !ok || v != 2 { | ||||||
|  | 			errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", 2) | ||||||
|  | 		} | ||||||
|  | 		if v, ok := (<-b).(string); !ok || v != "3" { | ||||||
|  | 			errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", "3") | ||||||
|  | 		} | ||||||
|  | 		if v, ok := (<-b).([]int); !ok || v[0] != 4 { | ||||||
|  | 			errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", []int{4}) | ||||||
|  | 		} | ||||||
|  | 		stopCh <- struct{}{} | ||||||
|  | 	}() | ||||||
|  |  | ||||||
|  | 	timeout := time.After(2 * time.Second) | ||||||
|  | 	for i := 0; i < 3; i++ { | ||||||
|  | 		select { | ||||||
|  | 		case <-timeout: | ||||||
|  | 			t.Fatal("Test timeout after 2s") | ||||||
|  | 		case e := <-errCh: | ||||||
|  | 			t.Fatal(e) | ||||||
|  | 		case <-stopCh: | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Test the unsubscription of channel | ||||||
|  | 	common.Must(c.Unsubscribe(b)) | ||||||
|  |  | ||||||
|  | 	// Test the last subscriber will close channel with `UnsubscribeClosableChannel` | ||||||
|  | 	common.Must(stats.UnsubscribeClosableChannel(c, a)) | ||||||
|  | 	if c.Running() { | ||||||
|  | 		t.Fatal("unexpected running channel after unsubscribing the last subscriber") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestStatsChannelUnsubcribe(t *testing.T) { | ||||||
|  | 	c := NewChannel(&ChannelConfig{Blocking: true}) | ||||||
|  | 	common.Must(c.Start()) | ||||||
|  | 	defer c.Close() | ||||||
|  |  | ||||||
|  | 	a, err := c.Subscribe() | ||||||
|  | 	common.Must(err) | ||||||
|  | 	defer c.Unsubscribe(a) | ||||||
|  |  | ||||||
|  | 	b, err := c.Subscribe() | ||||||
|  | 	common.Must(err) | ||||||
|  |  | ||||||
|  | 	pauseCh := make(chan struct{}) | ||||||
|  | 	stopCh := make(chan struct{}) | ||||||
|  | 	errCh := make(chan string) | ||||||
|  |  | ||||||
|  | 	{ | ||||||
|  | 		var aSet, bSet bool | ||||||
|  | 		for _, s := range c.Subscribers() { | ||||||
|  | 			if s == a { | ||||||
|  | 				aSet = true | ||||||
|  | 			} | ||||||
|  | 			if s == b { | ||||||
|  | 				bSet = true | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		if !(aSet && bSet) { | ||||||
|  | 			t.Fatal("unexpected subscribers: ", c.Subscribers()) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	go func() { // Blocking publish | ||||||
|  | 		c.Publish(context.Background(), 1) | ||||||
|  | 		<-pauseCh // Wait for `b` goroutine to resume sending message | ||||||
|  | 		c.Publish(context.Background(), 2) | ||||||
|  | 	}() | ||||||
|  |  | ||||||
|  | 	go func() { | ||||||
|  | 		if v, ok := (<-a).(int); !ok || v != 1 { | ||||||
|  | 			errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", 1) | ||||||
|  | 		} | ||||||
|  | 		if v, ok := (<-a).(int); !ok || v != 2 { | ||||||
|  | 			errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", 2) | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  |  | ||||||
|  | 	go func() { | ||||||
|  | 		if v, ok := (<-b).(int); !ok || v != 1 { | ||||||
|  | 			errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", 1) | ||||||
|  | 		} | ||||||
|  | 		// Unsubscribe `b` while publishing is paused | ||||||
|  | 		c.Unsubscribe(b) | ||||||
|  | 		{ // Test `b` is not in subscribers | ||||||
|  | 			var aSet, bSet bool | ||||||
|  | 			for _, s := range c.Subscribers() { | ||||||
|  | 				if s == a { | ||||||
|  | 					aSet = true | ||||||
|  | 				} | ||||||
|  | 				if s == b { | ||||||
|  | 					bSet = true | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			if !(aSet && !bSet) { | ||||||
|  | 				errCh <- fmt.Sprint("unexpected subscribers: ", c.Subscribers()) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		// Resume publishing progress | ||||||
|  | 		close(pauseCh) | ||||||
|  | 		// Test `b` is neither closed nor able to receive any data | ||||||
|  | 		select { | ||||||
|  | 		case v, ok := <-b: | ||||||
|  | 			if ok { | ||||||
|  | 				errCh <- fmt.Sprint("unexpected data received: ", v) | ||||||
|  | 			} else { | ||||||
|  | 				errCh <- fmt.Sprint("unexpected closed channel: ", b) | ||||||
|  | 			} | ||||||
|  | 		default: | ||||||
|  | 		} | ||||||
|  | 		close(stopCh) | ||||||
|  | 	}() | ||||||
|  |  | ||||||
|  | 	select { | ||||||
|  | 	case <-time.After(2 * time.Second): | ||||||
|  | 		t.Fatal("Test timeout after 2s") | ||||||
|  | 	case e := <-errCh: | ||||||
|  | 		t.Fatal(e) | ||||||
|  | 	case <-stopCh: | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestStatsChannelBlocking(t *testing.T) { | ||||||
|  | 	// Do not use buffer so as to create blocking scenario | ||||||
|  | 	c := NewChannel(&ChannelConfig{BufferSize: 0, Blocking: true}) | ||||||
|  | 	common.Must(c.Start()) | ||||||
|  | 	defer c.Close() | ||||||
|  |  | ||||||
|  | 	a, err := c.Subscribe() | ||||||
|  | 	common.Must(err) | ||||||
|  | 	defer c.Unsubscribe(a) | ||||||
|  |  | ||||||
|  | 	pauseCh := make(chan struct{}) | ||||||
|  | 	stopCh := make(chan struct{}) | ||||||
|  | 	errCh := make(chan string) | ||||||
|  |  | ||||||
|  | 	ctx, cancel := context.WithCancel(context.Background()) | ||||||
|  |  | ||||||
|  | 	// Test blocking channel publishing | ||||||
|  | 	go func() { | ||||||
|  | 		// Dummy messsage with no subscriber receiving, will block broadcasting goroutine | ||||||
|  | 		c.Publish(context.Background(), nil) | ||||||
|  |  | ||||||
|  | 		<-pauseCh | ||||||
|  |  | ||||||
|  | 		// Publishing should be blocked here, for last message was not cleared and buffer was full | ||||||
|  | 		c.Publish(context.Background(), nil) | ||||||
|  |  | ||||||
|  | 		pauseCh <- struct{}{} | ||||||
|  |  | ||||||
|  | 		// Publishing should still be blocked here | ||||||
|  | 		c.Publish(ctx, nil) | ||||||
|  |  | ||||||
|  | 		// Check publishing is done because context is canceled | ||||||
|  | 		select { | ||||||
|  | 		case <-ctx.Done(): | ||||||
|  | 			if ctx.Err() != context.Canceled { | ||||||
|  | 				errCh <- fmt.Sprint("unexpected error: ", ctx.Err()) | ||||||
|  | 			} | ||||||
|  | 		default: | ||||||
|  | 			errCh <- "unexpected non-blocked publishing" | ||||||
|  | 		} | ||||||
|  | 		close(stopCh) | ||||||
|  | 	}() | ||||||
|  |  | ||||||
|  | 	go func() { | ||||||
|  | 		pauseCh <- struct{}{} | ||||||
|  |  | ||||||
|  | 		select { | ||||||
|  | 		case <-pauseCh: | ||||||
|  | 			errCh <- "unexpected non-blocked publishing" | ||||||
|  | 		case <-time.After(100 * time.Millisecond): | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Receive first published message | ||||||
|  | 		<-a | ||||||
|  |  | ||||||
|  | 		select { | ||||||
|  | 		case <-pauseCh: | ||||||
|  | 		case <-time.After(100 * time.Millisecond): | ||||||
|  | 			errCh <- "unexpected blocking publishing" | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Manually cancel the context to end publishing | ||||||
|  | 		cancel() | ||||||
|  | 	}() | ||||||
|  |  | ||||||
|  | 	select { | ||||||
|  | 	case <-time.After(2 * time.Second): | ||||||
|  | 		t.Fatal("Test timeout after 2s") | ||||||
|  | 	case e := <-errCh: | ||||||
|  | 		t.Fatal(e) | ||||||
|  | 	case <-stopCh: | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestStatsChannelNonBlocking(t *testing.T) { | ||||||
|  | 	// Do not use buffer so as to create blocking scenario | ||||||
|  | 	c := NewChannel(&ChannelConfig{BufferSize: 0, Blocking: false}) | ||||||
|  | 	common.Must(c.Start()) | ||||||
|  | 	defer c.Close() | ||||||
|  |  | ||||||
|  | 	a, err := c.Subscribe() | ||||||
|  | 	common.Must(err) | ||||||
|  | 	defer c.Unsubscribe(a) | ||||||
|  |  | ||||||
|  | 	pauseCh := make(chan struct{}) | ||||||
|  | 	stopCh := make(chan struct{}) | ||||||
|  | 	errCh := make(chan string) | ||||||
|  |  | ||||||
|  | 	ctx, cancel := context.WithCancel(context.Background()) | ||||||
|  |  | ||||||
|  | 	// Test blocking channel publishing | ||||||
|  | 	go func() { | ||||||
|  | 		c.Publish(context.Background(), nil) | ||||||
|  | 		c.Publish(context.Background(), nil) | ||||||
|  | 		pauseCh <- struct{}{} | ||||||
|  | 		<-pauseCh | ||||||
|  | 		c.Publish(ctx, nil) | ||||||
|  | 		c.Publish(ctx, nil) | ||||||
|  | 		// Check publishing is done because context is canceled | ||||||
|  | 		select { | ||||||
|  | 		case <-ctx.Done(): | ||||||
|  | 			if ctx.Err() != context.Canceled { | ||||||
|  | 				errCh <- fmt.Sprint("unexpected error: ", ctx.Err()) | ||||||
|  | 			} | ||||||
|  | 		case <-time.After(100 * time.Millisecond): | ||||||
|  | 			errCh <- "unexpected non-cancelled publishing" | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  |  | ||||||
|  | 	go func() { | ||||||
|  | 		// Check publishing won't block even if there is no subscriber receiving message | ||||||
|  | 		select { | ||||||
|  | 		case <-pauseCh: | ||||||
|  | 		case <-time.After(100 * time.Millisecond): | ||||||
|  | 			errCh <- "unexpected blocking publishing" | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Receive first and second published message | ||||||
|  | 		<-a | ||||||
|  | 		<-a | ||||||
|  |  | ||||||
|  | 		pauseCh <- struct{}{} | ||||||
|  |  | ||||||
|  | 		// Manually cancel the context to end publishing | ||||||
|  | 		cancel() | ||||||
|  |  | ||||||
|  | 		// Check third and forth published message is cancelled and cannot receive | ||||||
|  | 		<-time.After(100 * time.Millisecond) | ||||||
|  | 		select { | ||||||
|  | 		case <-a: | ||||||
|  | 			errCh <- "unexpected non-cancelled publishing" | ||||||
|  | 		default: | ||||||
|  | 		} | ||||||
|  | 		select { | ||||||
|  | 		case <-a: | ||||||
|  | 			errCh <- "unexpected non-cancelled publishing" | ||||||
|  | 		default: | ||||||
|  | 		} | ||||||
|  | 		close(stopCh) | ||||||
|  | 	}() | ||||||
|  |  | ||||||
|  | 	select { | ||||||
|  | 	case <-time.After(2 * time.Second): | ||||||
|  | 		t.Fatal("Test timeout after 2s") | ||||||
|  | 	case e := <-errCh: | ||||||
|  | 		t.Fatal(e) | ||||||
|  | 	case <-stopCh: | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestStatsChannelConcurrency(t *testing.T) { | ||||||
|  | 	// Do not use buffer so as to create blocking scenario | ||||||
|  | 	c := NewChannel(&ChannelConfig{BufferSize: 0, Blocking: true}) | ||||||
|  | 	common.Must(c.Start()) | ||||||
|  | 	defer c.Close() | ||||||
|  |  | ||||||
|  | 	a, err := c.Subscribe() | ||||||
|  | 	common.Must(err) | ||||||
|  | 	defer c.Unsubscribe(a) | ||||||
|  |  | ||||||
|  | 	b, err := c.Subscribe() | ||||||
|  | 	common.Must(err) | ||||||
|  | 	defer c.Unsubscribe(b) | ||||||
|  |  | ||||||
|  | 	stopCh := make(chan struct{}) | ||||||
|  | 	errCh := make(chan string) | ||||||
|  |  | ||||||
|  | 	go func() { // Blocking publish | ||||||
|  | 		c.Publish(context.Background(), 1) | ||||||
|  | 		c.Publish(context.Background(), 2) | ||||||
|  | 	}() | ||||||
|  |  | ||||||
|  | 	go func() { | ||||||
|  | 		if v, ok := (<-a).(int); !ok || v != 1 { | ||||||
|  | 			errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", 1) | ||||||
|  | 		} | ||||||
|  | 		if v, ok := (<-a).(int); !ok || v != 2 { | ||||||
|  | 			errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", 2) | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  |  | ||||||
|  | 	go func() { | ||||||
|  | 		// Block `b` for a time so as to ensure source channel is trying to send message to `b`. | ||||||
|  | 		<-time.After(25 * time.Millisecond) | ||||||
|  | 		// This causes concurrency scenario: unsubscribe `b` while trying to send message to it | ||||||
|  | 		c.Unsubscribe(b) | ||||||
|  | 		// Test `b` is not closed and can still receive data 1: | ||||||
|  | 		// Because unsubscribe won't affect the ongoing process of sending message. | ||||||
|  | 		select { | ||||||
|  | 		case v, ok := <-b: | ||||||
|  | 			if v1, ok1 := v.(int); !(ok && ok1 && v1 == 1) { | ||||||
|  | 				errCh <- fmt.Sprint("unexpected failure in receiving data: ", 1) | ||||||
|  | 			} | ||||||
|  | 		default: | ||||||
|  | 			errCh <- fmt.Sprint("unexpected block from receiving data: ", 1) | ||||||
|  | 		} | ||||||
|  | 		// Test `b` is not closed but cannot receive data 2: | ||||||
|  | 		// Because in a new round of messaging, `b` has been unsubscribed. | ||||||
|  | 		select { | ||||||
|  | 		case v, ok := <-b: | ||||||
|  | 			if ok { | ||||||
|  | 				errCh <- fmt.Sprint("unexpected receiving: ", v) | ||||||
|  | 			} else { | ||||||
|  | 				errCh <- "unexpected closing of channel" | ||||||
|  | 			} | ||||||
|  | 		default: | ||||||
|  | 		} | ||||||
|  | 		close(stopCh) | ||||||
|  | 	}() | ||||||
|  |  | ||||||
|  | 	select { | ||||||
|  | 	case <-time.After(2 * time.Second): | ||||||
|  | 		t.Fatal("Test timeout after 2s") | ||||||
|  | 	case e := <-errCh: | ||||||
|  | 		t.Fatal(e) | ||||||
|  | 	case <-stopCh: | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										127
									
								
								app/stats/command/command.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								app/stats/command/command.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,127 @@ | |||||||
|  | // +build !confonly | ||||||
|  |  | ||||||
|  | package command | ||||||
|  |  | ||||||
|  | //go:generate go run github.com/xtls/xray-core/v1/common/errors/errorgen | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"runtime" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	grpc "google.golang.org/grpc" | ||||||
|  |  | ||||||
|  | 	"github.com/xtls/xray-core/v1/app/stats" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common" | ||||||
|  | 	"github.com/xtls/xray-core/v1/common/strmatcher" | ||||||
|  | 	"github.com/xtls/xray-core/v1/core" | ||||||
|  | 	feature_stats "github.com/xtls/xray-core/v1/features/stats" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // statsServer is an implementation of StatsService. | ||||||
|  | type statsServer struct { | ||||||
|  | 	stats     feature_stats.Manager | ||||||
|  | 	startTime time.Time | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewStatsServer(manager feature_stats.Manager) StatsServiceServer { | ||||||
|  | 	return &statsServer{ | ||||||
|  | 		stats:     manager, | ||||||
|  | 		startTime: time.Now(), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *statsServer) GetStats(ctx context.Context, request *GetStatsRequest) (*GetStatsResponse, error) { | ||||||
|  | 	c := s.stats.GetCounter(request.Name) | ||||||
|  | 	if c == nil { | ||||||
|  | 		return nil, newError(request.Name, " not found.") | ||||||
|  | 	} | ||||||
|  | 	var value int64 | ||||||
|  | 	if request.Reset_ { | ||||||
|  | 		value = c.Set(0) | ||||||
|  | 	} else { | ||||||
|  | 		value = c.Value() | ||||||
|  | 	} | ||||||
|  | 	return &GetStatsResponse{ | ||||||
|  | 		Stat: &Stat{ | ||||||
|  | 			Name:  request.Name, | ||||||
|  | 			Value: value, | ||||||
|  | 		}, | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *statsServer) QueryStats(ctx context.Context, request *QueryStatsRequest) (*QueryStatsResponse, error) { | ||||||
|  | 	matcher, err := strmatcher.Substr.New(request.Pattern) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	response := &QueryStatsResponse{} | ||||||
|  |  | ||||||
|  | 	manager, ok := s.stats.(*stats.Manager) | ||||||
|  | 	if !ok { | ||||||
|  | 		return nil, newError("QueryStats only works its own stats.Manager.") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	manager.VisitCounters(func(name string, c feature_stats.Counter) bool { | ||||||
|  | 		if matcher.Match(name) { | ||||||
|  | 			var value int64 | ||||||
|  | 			if request.Reset_ { | ||||||
|  | 				value = c.Set(0) | ||||||
|  | 			} else { | ||||||
|  | 				value = c.Value() | ||||||
|  | 			} | ||||||
|  | 			response.Stat = append(response.Stat, &Stat{ | ||||||
|  | 				Name:  name, | ||||||
|  | 				Value: value, | ||||||
|  | 			}) | ||||||
|  | 		} | ||||||
|  | 		return true | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	return response, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *statsServer) GetSysStats(ctx context.Context, request *SysStatsRequest) (*SysStatsResponse, error) { | ||||||
|  | 	var rtm runtime.MemStats | ||||||
|  | 	runtime.ReadMemStats(&rtm) | ||||||
|  |  | ||||||
|  | 	uptime := time.Since(s.startTime) | ||||||
|  |  | ||||||
|  | 	response := &SysStatsResponse{ | ||||||
|  | 		Uptime:       uint32(uptime.Seconds()), | ||||||
|  | 		NumGoroutine: uint32(runtime.NumGoroutine()), | ||||||
|  | 		Alloc:        rtm.Alloc, | ||||||
|  | 		TotalAlloc:   rtm.TotalAlloc, | ||||||
|  | 		Sys:          rtm.Sys, | ||||||
|  | 		Mallocs:      rtm.Mallocs, | ||||||
|  | 		Frees:        rtm.Frees, | ||||||
|  | 		LiveObjects:  rtm.Mallocs - rtm.Frees, | ||||||
|  | 		NumGC:        rtm.NumGC, | ||||||
|  | 		PauseTotalNs: rtm.PauseTotalNs, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return response, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *statsServer) mustEmbedUnimplementedStatsServiceServer() {} | ||||||
|  |  | ||||||
|  | type service struct { | ||||||
|  | 	statsManager feature_stats.Manager | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *service) Register(server *grpc.Server) { | ||||||
|  | 	RegisterStatsServiceServer(server, NewStatsServer(s.statsManager)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func init() { | ||||||
|  | 	common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, cfg interface{}) (interface{}, error) { | ||||||
|  | 		s := new(service) | ||||||
|  |  | ||||||
|  | 		core.RequireFeatures(ctx, func(sm feature_stats.Manager) { | ||||||
|  | 			s.statsManager = sm | ||||||
|  | 		}) | ||||||
|  |  | ||||||
|  | 		return s, nil | ||||||
|  | 	})) | ||||||
|  | } | ||||||
							
								
								
									
										720
									
								
								app/stats/command/command.pb.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										720
									
								
								app/stats/command/command.pb.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,720 @@ | |||||||
|  | // Code generated by protoc-gen-go. DO NOT EDIT. | ||||||
|  | // versions: | ||||||
|  | // 	protoc-gen-go v1.25.0 | ||||||
|  | // 	protoc        v3.14.0 | ||||||
|  | // source: app/stats/command/command.proto | ||||||
|  |  | ||||||
|  | package command | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	proto "github.com/golang/protobuf/proto" | ||||||
|  | 	protoreflect "google.golang.org/protobuf/reflect/protoreflect" | ||||||
|  | 	protoimpl "google.golang.org/protobuf/runtime/protoimpl" | ||||||
|  | 	reflect "reflect" | ||||||
|  | 	sync "sync" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	// Verify that this generated code is sufficiently up-to-date. | ||||||
|  | 	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) | ||||||
|  | 	// Verify that runtime/protoimpl is sufficiently up-to-date. | ||||||
|  | 	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // This is a compile-time assertion that a sufficiently up-to-date version | ||||||
|  | // of the legacy proto package is being used. | ||||||
|  | const _ = proto.ProtoPackageIsVersion4 | ||||||
|  |  | ||||||
|  | type GetStatsRequest struct { | ||||||
|  | 	state         protoimpl.MessageState | ||||||
|  | 	sizeCache     protoimpl.SizeCache | ||||||
|  | 	unknownFields protoimpl.UnknownFields | ||||||
|  |  | ||||||
|  | 	// Name of the stat counter. | ||||||
|  | 	Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` | ||||||
|  | 	// Whether or not to reset the counter to fetching its value. | ||||||
|  | 	Reset_ bool `protobuf:"varint,2,opt,name=reset,proto3" json:"reset,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *GetStatsRequest) Reset() { | ||||||
|  | 	*x = GetStatsRequest{} | ||||||
|  | 	if protoimpl.UnsafeEnabled { | ||||||
|  | 		mi := &file_app_stats_command_command_proto_msgTypes[0] | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		ms.StoreMessageInfo(mi) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *GetStatsRequest) String() string { | ||||||
|  | 	return protoimpl.X.MessageStringOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (*GetStatsRequest) ProtoMessage() {} | ||||||
|  |  | ||||||
|  | func (x *GetStatsRequest) ProtoReflect() protoreflect.Message { | ||||||
|  | 	mi := &file_app_stats_command_command_proto_msgTypes[0] | ||||||
|  | 	if protoimpl.UnsafeEnabled && x != nil { | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		if ms.LoadMessageInfo() == nil { | ||||||
|  | 			ms.StoreMessageInfo(mi) | ||||||
|  | 		} | ||||||
|  | 		return ms | ||||||
|  | 	} | ||||||
|  | 	return mi.MessageOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Deprecated: Use GetStatsRequest.ProtoReflect.Descriptor instead. | ||||||
|  | func (*GetStatsRequest) Descriptor() ([]byte, []int) { | ||||||
|  | 	return file_app_stats_command_command_proto_rawDescGZIP(), []int{0} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *GetStatsRequest) GetName() string { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Name | ||||||
|  | 	} | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *GetStatsRequest) GetReset_() bool { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Reset_ | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Stat struct { | ||||||
|  | 	state         protoimpl.MessageState | ||||||
|  | 	sizeCache     protoimpl.SizeCache | ||||||
|  | 	unknownFields protoimpl.UnknownFields | ||||||
|  |  | ||||||
|  | 	Name  string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` | ||||||
|  | 	Value int64  `protobuf:"varint,2,opt,name=value,proto3" json:"value,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Stat) Reset() { | ||||||
|  | 	*x = Stat{} | ||||||
|  | 	if protoimpl.UnsafeEnabled { | ||||||
|  | 		mi := &file_app_stats_command_command_proto_msgTypes[1] | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		ms.StoreMessageInfo(mi) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Stat) String() string { | ||||||
|  | 	return protoimpl.X.MessageStringOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (*Stat) ProtoMessage() {} | ||||||
|  |  | ||||||
|  | func (x *Stat) ProtoReflect() protoreflect.Message { | ||||||
|  | 	mi := &file_app_stats_command_command_proto_msgTypes[1] | ||||||
|  | 	if protoimpl.UnsafeEnabled && x != nil { | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		if ms.LoadMessageInfo() == nil { | ||||||
|  | 			ms.StoreMessageInfo(mi) | ||||||
|  | 		} | ||||||
|  | 		return ms | ||||||
|  | 	} | ||||||
|  | 	return mi.MessageOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Deprecated: Use Stat.ProtoReflect.Descriptor instead. | ||||||
|  | func (*Stat) Descriptor() ([]byte, []int) { | ||||||
|  | 	return file_app_stats_command_command_proto_rawDescGZIP(), []int{1} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Stat) GetName() string { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Name | ||||||
|  | 	} | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Stat) GetValue() int64 { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Value | ||||||
|  | 	} | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type GetStatsResponse struct { | ||||||
|  | 	state         protoimpl.MessageState | ||||||
|  | 	sizeCache     protoimpl.SizeCache | ||||||
|  | 	unknownFields protoimpl.UnknownFields | ||||||
|  |  | ||||||
|  | 	Stat *Stat `protobuf:"bytes,1,opt,name=stat,proto3" json:"stat,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *GetStatsResponse) Reset() { | ||||||
|  | 	*x = GetStatsResponse{} | ||||||
|  | 	if protoimpl.UnsafeEnabled { | ||||||
|  | 		mi := &file_app_stats_command_command_proto_msgTypes[2] | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		ms.StoreMessageInfo(mi) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *GetStatsResponse) String() string { | ||||||
|  | 	return protoimpl.X.MessageStringOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (*GetStatsResponse) ProtoMessage() {} | ||||||
|  |  | ||||||
|  | func (x *GetStatsResponse) ProtoReflect() protoreflect.Message { | ||||||
|  | 	mi := &file_app_stats_command_command_proto_msgTypes[2] | ||||||
|  | 	if protoimpl.UnsafeEnabled && x != nil { | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		if ms.LoadMessageInfo() == nil { | ||||||
|  | 			ms.StoreMessageInfo(mi) | ||||||
|  | 		} | ||||||
|  | 		return ms | ||||||
|  | 	} | ||||||
|  | 	return mi.MessageOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Deprecated: Use GetStatsResponse.ProtoReflect.Descriptor instead. | ||||||
|  | func (*GetStatsResponse) Descriptor() ([]byte, []int) { | ||||||
|  | 	return file_app_stats_command_command_proto_rawDescGZIP(), []int{2} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *GetStatsResponse) GetStat() *Stat { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Stat | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type QueryStatsRequest struct { | ||||||
|  | 	state         protoimpl.MessageState | ||||||
|  | 	sizeCache     protoimpl.SizeCache | ||||||
|  | 	unknownFields protoimpl.UnknownFields | ||||||
|  |  | ||||||
|  | 	Pattern string `protobuf:"bytes,1,opt,name=pattern,proto3" json:"pattern,omitempty"` | ||||||
|  | 	Reset_  bool   `protobuf:"varint,2,opt,name=reset,proto3" json:"reset,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *QueryStatsRequest) Reset() { | ||||||
|  | 	*x = QueryStatsRequest{} | ||||||
|  | 	if protoimpl.UnsafeEnabled { | ||||||
|  | 		mi := &file_app_stats_command_command_proto_msgTypes[3] | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		ms.StoreMessageInfo(mi) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *QueryStatsRequest) String() string { | ||||||
|  | 	return protoimpl.X.MessageStringOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (*QueryStatsRequest) ProtoMessage() {} | ||||||
|  |  | ||||||
|  | func (x *QueryStatsRequest) ProtoReflect() protoreflect.Message { | ||||||
|  | 	mi := &file_app_stats_command_command_proto_msgTypes[3] | ||||||
|  | 	if protoimpl.UnsafeEnabled && x != nil { | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		if ms.LoadMessageInfo() == nil { | ||||||
|  | 			ms.StoreMessageInfo(mi) | ||||||
|  | 		} | ||||||
|  | 		return ms | ||||||
|  | 	} | ||||||
|  | 	return mi.MessageOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Deprecated: Use QueryStatsRequest.ProtoReflect.Descriptor instead. | ||||||
|  | func (*QueryStatsRequest) Descriptor() ([]byte, []int) { | ||||||
|  | 	return file_app_stats_command_command_proto_rawDescGZIP(), []int{3} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *QueryStatsRequest) GetPattern() string { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Pattern | ||||||
|  | 	} | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *QueryStatsRequest) GetReset_() bool { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Reset_ | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type QueryStatsResponse struct { | ||||||
|  | 	state         protoimpl.MessageState | ||||||
|  | 	sizeCache     protoimpl.SizeCache | ||||||
|  | 	unknownFields protoimpl.UnknownFields | ||||||
|  |  | ||||||
|  | 	Stat []*Stat `protobuf:"bytes,1,rep,name=stat,proto3" json:"stat,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *QueryStatsResponse) Reset() { | ||||||
|  | 	*x = QueryStatsResponse{} | ||||||
|  | 	if protoimpl.UnsafeEnabled { | ||||||
|  | 		mi := &file_app_stats_command_command_proto_msgTypes[4] | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		ms.StoreMessageInfo(mi) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *QueryStatsResponse) String() string { | ||||||
|  | 	return protoimpl.X.MessageStringOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (*QueryStatsResponse) ProtoMessage() {} | ||||||
|  |  | ||||||
|  | func (x *QueryStatsResponse) ProtoReflect() protoreflect.Message { | ||||||
|  | 	mi := &file_app_stats_command_command_proto_msgTypes[4] | ||||||
|  | 	if protoimpl.UnsafeEnabled && x != nil { | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		if ms.LoadMessageInfo() == nil { | ||||||
|  | 			ms.StoreMessageInfo(mi) | ||||||
|  | 		} | ||||||
|  | 		return ms | ||||||
|  | 	} | ||||||
|  | 	return mi.MessageOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Deprecated: Use QueryStatsResponse.ProtoReflect.Descriptor instead. | ||||||
|  | func (*QueryStatsResponse) Descriptor() ([]byte, []int) { | ||||||
|  | 	return file_app_stats_command_command_proto_rawDescGZIP(), []int{4} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *QueryStatsResponse) GetStat() []*Stat { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Stat | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type SysStatsRequest struct { | ||||||
|  | 	state         protoimpl.MessageState | ||||||
|  | 	sizeCache     protoimpl.SizeCache | ||||||
|  | 	unknownFields protoimpl.UnknownFields | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *SysStatsRequest) Reset() { | ||||||
|  | 	*x = SysStatsRequest{} | ||||||
|  | 	if protoimpl.UnsafeEnabled { | ||||||
|  | 		mi := &file_app_stats_command_command_proto_msgTypes[5] | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		ms.StoreMessageInfo(mi) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *SysStatsRequest) String() string { | ||||||
|  | 	return protoimpl.X.MessageStringOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (*SysStatsRequest) ProtoMessage() {} | ||||||
|  |  | ||||||
|  | func (x *SysStatsRequest) ProtoReflect() protoreflect.Message { | ||||||
|  | 	mi := &file_app_stats_command_command_proto_msgTypes[5] | ||||||
|  | 	if protoimpl.UnsafeEnabled && x != nil { | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		if ms.LoadMessageInfo() == nil { | ||||||
|  | 			ms.StoreMessageInfo(mi) | ||||||
|  | 		} | ||||||
|  | 		return ms | ||||||
|  | 	} | ||||||
|  | 	return mi.MessageOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Deprecated: Use SysStatsRequest.ProtoReflect.Descriptor instead. | ||||||
|  | func (*SysStatsRequest) Descriptor() ([]byte, []int) { | ||||||
|  | 	return file_app_stats_command_command_proto_rawDescGZIP(), []int{5} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type SysStatsResponse struct { | ||||||
|  | 	state         protoimpl.MessageState | ||||||
|  | 	sizeCache     protoimpl.SizeCache | ||||||
|  | 	unknownFields protoimpl.UnknownFields | ||||||
|  |  | ||||||
|  | 	NumGoroutine uint32 `protobuf:"varint,1,opt,name=NumGoroutine,proto3" json:"NumGoroutine,omitempty"` | ||||||
|  | 	NumGC        uint32 `protobuf:"varint,2,opt,name=NumGC,proto3" json:"NumGC,omitempty"` | ||||||
|  | 	Alloc        uint64 `protobuf:"varint,3,opt,name=Alloc,proto3" json:"Alloc,omitempty"` | ||||||
|  | 	TotalAlloc   uint64 `protobuf:"varint,4,opt,name=TotalAlloc,proto3" json:"TotalAlloc,omitempty"` | ||||||
|  | 	Sys          uint64 `protobuf:"varint,5,opt,name=Sys,proto3" json:"Sys,omitempty"` | ||||||
|  | 	Mallocs      uint64 `protobuf:"varint,6,opt,name=Mallocs,proto3" json:"Mallocs,omitempty"` | ||||||
|  | 	Frees        uint64 `protobuf:"varint,7,opt,name=Frees,proto3" json:"Frees,omitempty"` | ||||||
|  | 	LiveObjects  uint64 `protobuf:"varint,8,opt,name=LiveObjects,proto3" json:"LiveObjects,omitempty"` | ||||||
|  | 	PauseTotalNs uint64 `protobuf:"varint,9,opt,name=PauseTotalNs,proto3" json:"PauseTotalNs,omitempty"` | ||||||
|  | 	Uptime       uint32 `protobuf:"varint,10,opt,name=Uptime,proto3" json:"Uptime,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *SysStatsResponse) Reset() { | ||||||
|  | 	*x = SysStatsResponse{} | ||||||
|  | 	if protoimpl.UnsafeEnabled { | ||||||
|  | 		mi := &file_app_stats_command_command_proto_msgTypes[6] | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		ms.StoreMessageInfo(mi) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *SysStatsResponse) String() string { | ||||||
|  | 	return protoimpl.X.MessageStringOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (*SysStatsResponse) ProtoMessage() {} | ||||||
|  |  | ||||||
|  | func (x *SysStatsResponse) ProtoReflect() protoreflect.Message { | ||||||
|  | 	mi := &file_app_stats_command_command_proto_msgTypes[6] | ||||||
|  | 	if protoimpl.UnsafeEnabled && x != nil { | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		if ms.LoadMessageInfo() == nil { | ||||||
|  | 			ms.StoreMessageInfo(mi) | ||||||
|  | 		} | ||||||
|  | 		return ms | ||||||
|  | 	} | ||||||
|  | 	return mi.MessageOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Deprecated: Use SysStatsResponse.ProtoReflect.Descriptor instead. | ||||||
|  | func (*SysStatsResponse) Descriptor() ([]byte, []int) { | ||||||
|  | 	return file_app_stats_command_command_proto_rawDescGZIP(), []int{6} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *SysStatsResponse) GetNumGoroutine() uint32 { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.NumGoroutine | ||||||
|  | 	} | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *SysStatsResponse) GetNumGC() uint32 { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.NumGC | ||||||
|  | 	} | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *SysStatsResponse) GetAlloc() uint64 { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Alloc | ||||||
|  | 	} | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *SysStatsResponse) GetTotalAlloc() uint64 { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.TotalAlloc | ||||||
|  | 	} | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *SysStatsResponse) GetSys() uint64 { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Sys | ||||||
|  | 	} | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *SysStatsResponse) GetMallocs() uint64 { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Mallocs | ||||||
|  | 	} | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *SysStatsResponse) GetFrees() uint64 { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Frees | ||||||
|  | 	} | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *SysStatsResponse) GetLiveObjects() uint64 { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.LiveObjects | ||||||
|  | 	} | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *SysStatsResponse) GetPauseTotalNs() uint64 { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.PauseTotalNs | ||||||
|  | 	} | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *SysStatsResponse) GetUptime() uint32 { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Uptime | ||||||
|  | 	} | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Config struct { | ||||||
|  | 	state         protoimpl.MessageState | ||||||
|  | 	sizeCache     protoimpl.SizeCache | ||||||
|  | 	unknownFields protoimpl.UnknownFields | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Config) Reset() { | ||||||
|  | 	*x = Config{} | ||||||
|  | 	if protoimpl.UnsafeEnabled { | ||||||
|  | 		mi := &file_app_stats_command_command_proto_msgTypes[7] | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		ms.StoreMessageInfo(mi) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Config) String() string { | ||||||
|  | 	return protoimpl.X.MessageStringOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (*Config) ProtoMessage() {} | ||||||
|  |  | ||||||
|  | func (x *Config) ProtoReflect() protoreflect.Message { | ||||||
|  | 	mi := &file_app_stats_command_command_proto_msgTypes[7] | ||||||
|  | 	if protoimpl.UnsafeEnabled && x != nil { | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		if ms.LoadMessageInfo() == nil { | ||||||
|  | 			ms.StoreMessageInfo(mi) | ||||||
|  | 		} | ||||||
|  | 		return ms | ||||||
|  | 	} | ||||||
|  | 	return mi.MessageOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Deprecated: Use Config.ProtoReflect.Descriptor instead. | ||||||
|  | func (*Config) Descriptor() ([]byte, []int) { | ||||||
|  | 	return file_app_stats_command_command_proto_rawDescGZIP(), []int{7} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var File_app_stats_command_command_proto protoreflect.FileDescriptor | ||||||
|  |  | ||||||
|  | var file_app_stats_command_command_proto_rawDesc = []byte{ | ||||||
|  | 	0x0a, 0x1f, 0x61, 0x70, 0x70, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, | ||||||
|  | 	0x61, 0x6e, 0x64, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, | ||||||
|  | 	0x6f, 0x12, 0x16, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, | ||||||
|  | 	0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x22, 0x3b, 0x0a, 0x0f, 0x47, 0x65, 0x74, | ||||||
|  | 	0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, | ||||||
|  | 	0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, | ||||||
|  | 	0x12, 0x14, 0x0a, 0x05, 0x72, 0x65, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, | ||||||
|  | 	0x05, 0x72, 0x65, 0x73, 0x65, 0x74, 0x22, 0x30, 0x0a, 0x04, 0x53, 0x74, 0x61, 0x74, 0x12, 0x12, | ||||||
|  | 	0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, | ||||||
|  | 	0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, | ||||||
|  | 	0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x44, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x53, | ||||||
|  | 	0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x30, 0x0a, 0x04, | ||||||
|  | 	0x73, 0x74, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x78, 0x72, 0x61, | ||||||
|  | 	0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, | ||||||
|  | 	0x61, 0x6e, 0x64, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x52, 0x04, 0x73, 0x74, 0x61, 0x74, 0x22, 0x43, | ||||||
|  | 	0x0a, 0x11, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, | ||||||
|  | 	0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x18, 0x01, | ||||||
|  | 	0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x12, 0x14, 0x0a, | ||||||
|  | 	0x05, 0x72, 0x65, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x72, 0x65, | ||||||
|  | 	0x73, 0x65, 0x74, 0x22, 0x46, 0x0a, 0x12, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, | ||||||
|  | 	0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x30, 0x0a, 0x04, 0x73, 0x74, 0x61, | ||||||
|  | 	0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, | ||||||
|  | 	0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, | ||||||
|  | 	0x2e, 0x53, 0x74, 0x61, 0x74, 0x52, 0x04, 0x73, 0x74, 0x61, 0x74, 0x22, 0x11, 0x0a, 0x0f, 0x53, | ||||||
|  | 	0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xa2, | ||||||
|  | 	0x02, 0x0a, 0x10, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, | ||||||
|  | 	0x6e, 0x73, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x4e, 0x75, 0x6d, 0x47, 0x6f, 0x72, 0x6f, 0x75, 0x74, | ||||||
|  | 	0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x4e, 0x75, 0x6d, 0x47, 0x6f, | ||||||
|  | 	0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x4e, 0x75, 0x6d, 0x47, 0x43, | ||||||
|  | 	0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x4e, 0x75, 0x6d, 0x47, 0x43, 0x12, 0x14, 0x0a, | ||||||
|  | 	0x05, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x41, 0x6c, | ||||||
|  | 	0x6c, 0x6f, 0x63, 0x12, 0x1e, 0x0a, 0x0a, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6c, 0x6c, 0x6f, | ||||||
|  | 	0x63, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6c, | ||||||
|  | 	0x6c, 0x6f, 0x63, 0x12, 0x10, 0x0a, 0x03, 0x53, 0x79, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, | ||||||
|  | 	0x52, 0x03, 0x53, 0x79, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x4d, 0x61, 0x6c, 0x6c, 0x6f, 0x63, 0x73, | ||||||
|  | 	0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x4d, 0x61, 0x6c, 0x6c, 0x6f, 0x63, 0x73, 0x12, | ||||||
|  | 	0x14, 0x0a, 0x05, 0x46, 0x72, 0x65, 0x65, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, | ||||||
|  | 	0x46, 0x72, 0x65, 0x65, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x4c, 0x69, 0x76, 0x65, 0x4f, 0x62, 0x6a, | ||||||
|  | 	0x65, 0x63, 0x74, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x4c, 0x69, 0x76, 0x65, | ||||||
|  | 	0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x22, 0x0a, 0x0c, 0x50, 0x61, 0x75, 0x73, 0x65, | ||||||
|  | 	0x54, 0x6f, 0x74, 0x61, 0x6c, 0x4e, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x50, | ||||||
|  | 	0x61, 0x75, 0x73, 0x65, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x4e, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x55, | ||||||
|  | 	0x70, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x55, 0x70, 0x74, | ||||||
|  | 	0x69, 0x6d, 0x65, 0x22, 0x08, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x32, 0xba, 0x02, | ||||||
|  | 	0x0a, 0x0c, 0x53, 0x74, 0x61, 0x74, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5f, | ||||||
|  | 	0x0a, 0x08, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x27, 0x2e, 0x78, 0x72, 0x61, | ||||||
|  | 	0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, | ||||||
|  | 	0x61, 0x6e, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, | ||||||
|  | 	0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, | ||||||
|  | 	0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x47, 0x65, 0x74, | ||||||
|  | 	0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, | ||||||
|  | 	0x65, 0x0a, 0x0a, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x29, 0x2e, | ||||||
|  | 	0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, | ||||||
|  | 	0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, | ||||||
|  | 	0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, | ||||||
|  | 	0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, | ||||||
|  | 	0x64, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, | ||||||
|  | 	0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x62, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x53, 0x79, 0x73, | ||||||
|  | 	0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x27, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, | ||||||
|  | 	0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x53, | ||||||
|  | 	0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, | ||||||
|  | 	0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, | ||||||
|  | 	0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, | ||||||
|  | 	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x67, 0x0a, 0x1a, 0x63, 0x6f, | ||||||
|  | 	0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, | ||||||
|  | 	0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x50, 0x01, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, | ||||||
|  | 	0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, | ||||||
|  | 	0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x73, 0x74, 0x61, | ||||||
|  | 	0x74, 0x73, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0xaa, 0x02, 0x16, 0x58, 0x72, 0x61, | ||||||
|  | 	0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, | ||||||
|  | 	0x61, 0x6e, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var ( | ||||||
|  | 	file_app_stats_command_command_proto_rawDescOnce sync.Once | ||||||
|  | 	file_app_stats_command_command_proto_rawDescData = file_app_stats_command_command_proto_rawDesc | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func file_app_stats_command_command_proto_rawDescGZIP() []byte { | ||||||
|  | 	file_app_stats_command_command_proto_rawDescOnce.Do(func() { | ||||||
|  | 		file_app_stats_command_command_proto_rawDescData = protoimpl.X.CompressGZIP(file_app_stats_command_command_proto_rawDescData) | ||||||
|  | 	}) | ||||||
|  | 	return file_app_stats_command_command_proto_rawDescData | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var file_app_stats_command_command_proto_msgTypes = make([]protoimpl.MessageInfo, 8) | ||||||
|  | var file_app_stats_command_command_proto_goTypes = []interface{}{ | ||||||
|  | 	(*GetStatsRequest)(nil),    // 0: xray.app.stats.command.GetStatsRequest | ||||||
|  | 	(*Stat)(nil),               // 1: xray.app.stats.command.Stat | ||||||
|  | 	(*GetStatsResponse)(nil),   // 2: xray.app.stats.command.GetStatsResponse | ||||||
|  | 	(*QueryStatsRequest)(nil),  // 3: xray.app.stats.command.QueryStatsRequest | ||||||
|  | 	(*QueryStatsResponse)(nil), // 4: xray.app.stats.command.QueryStatsResponse | ||||||
|  | 	(*SysStatsRequest)(nil),    // 5: xray.app.stats.command.SysStatsRequest | ||||||
|  | 	(*SysStatsResponse)(nil),   // 6: xray.app.stats.command.SysStatsResponse | ||||||
|  | 	(*Config)(nil),             // 7: xray.app.stats.command.Config | ||||||
|  | } | ||||||
|  | var file_app_stats_command_command_proto_depIdxs = []int32{ | ||||||
|  | 	1, // 0: xray.app.stats.command.GetStatsResponse.stat:type_name -> xray.app.stats.command.Stat | ||||||
|  | 	1, // 1: xray.app.stats.command.QueryStatsResponse.stat:type_name -> xray.app.stats.command.Stat | ||||||
|  | 	0, // 2: xray.app.stats.command.StatsService.GetStats:input_type -> xray.app.stats.command.GetStatsRequest | ||||||
|  | 	3, // 3: xray.app.stats.command.StatsService.QueryStats:input_type -> xray.app.stats.command.QueryStatsRequest | ||||||
|  | 	5, // 4: xray.app.stats.command.StatsService.GetSysStats:input_type -> xray.app.stats.command.SysStatsRequest | ||||||
|  | 	2, // 5: xray.app.stats.command.StatsService.GetStats:output_type -> xray.app.stats.command.GetStatsResponse | ||||||
|  | 	4, // 6: xray.app.stats.command.StatsService.QueryStats:output_type -> xray.app.stats.command.QueryStatsResponse | ||||||
|  | 	6, // 7: xray.app.stats.command.StatsService.GetSysStats:output_type -> xray.app.stats.command.SysStatsResponse | ||||||
|  | 	5, // [5:8] is the sub-list for method output_type | ||||||
|  | 	2, // [2:5] is the sub-list for method input_type | ||||||
|  | 	2, // [2:2] is the sub-list for extension type_name | ||||||
|  | 	2, // [2:2] is the sub-list for extension extendee | ||||||
|  | 	0, // [0:2] is the sub-list for field type_name | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func init() { file_app_stats_command_command_proto_init() } | ||||||
|  | func file_app_stats_command_command_proto_init() { | ||||||
|  | 	if File_app_stats_command_command_proto != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if !protoimpl.UnsafeEnabled { | ||||||
|  | 		file_app_stats_command_command_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { | ||||||
|  | 			switch v := v.(*GetStatsRequest); i { | ||||||
|  | 			case 0: | ||||||
|  | 				return &v.state | ||||||
|  | 			case 1: | ||||||
|  | 				return &v.sizeCache | ||||||
|  | 			case 2: | ||||||
|  | 				return &v.unknownFields | ||||||
|  | 			default: | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		file_app_stats_command_command_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { | ||||||
|  | 			switch v := v.(*Stat); i { | ||||||
|  | 			case 0: | ||||||
|  | 				return &v.state | ||||||
|  | 			case 1: | ||||||
|  | 				return &v.sizeCache | ||||||
|  | 			case 2: | ||||||
|  | 				return &v.unknownFields | ||||||
|  | 			default: | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		file_app_stats_command_command_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { | ||||||
|  | 			switch v := v.(*GetStatsResponse); i { | ||||||
|  | 			case 0: | ||||||
|  | 				return &v.state | ||||||
|  | 			case 1: | ||||||
|  | 				return &v.sizeCache | ||||||
|  | 			case 2: | ||||||
|  | 				return &v.unknownFields | ||||||
|  | 			default: | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		file_app_stats_command_command_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { | ||||||
|  | 			switch v := v.(*QueryStatsRequest); i { | ||||||
|  | 			case 0: | ||||||
|  | 				return &v.state | ||||||
|  | 			case 1: | ||||||
|  | 				return &v.sizeCache | ||||||
|  | 			case 2: | ||||||
|  | 				return &v.unknownFields | ||||||
|  | 			default: | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		file_app_stats_command_command_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { | ||||||
|  | 			switch v := v.(*QueryStatsResponse); i { | ||||||
|  | 			case 0: | ||||||
|  | 				return &v.state | ||||||
|  | 			case 1: | ||||||
|  | 				return &v.sizeCache | ||||||
|  | 			case 2: | ||||||
|  | 				return &v.unknownFields | ||||||
|  | 			default: | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		file_app_stats_command_command_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { | ||||||
|  | 			switch v := v.(*SysStatsRequest); i { | ||||||
|  | 			case 0: | ||||||
|  | 				return &v.state | ||||||
|  | 			case 1: | ||||||
|  | 				return &v.sizeCache | ||||||
|  | 			case 2: | ||||||
|  | 				return &v.unknownFields | ||||||
|  | 			default: | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		file_app_stats_command_command_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { | ||||||
|  | 			switch v := v.(*SysStatsResponse); i { | ||||||
|  | 			case 0: | ||||||
|  | 				return &v.state | ||||||
|  | 			case 1: | ||||||
|  | 				return &v.sizeCache | ||||||
|  | 			case 2: | ||||||
|  | 				return &v.unknownFields | ||||||
|  | 			default: | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		file_app_stats_command_command_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { | ||||||
|  | 			switch v := v.(*Config); i { | ||||||
|  | 			case 0: | ||||||
|  | 				return &v.state | ||||||
|  | 			case 1: | ||||||
|  | 				return &v.sizeCache | ||||||
|  | 			case 2: | ||||||
|  | 				return &v.unknownFields | ||||||
|  | 			default: | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	type x struct{} | ||||||
|  | 	out := protoimpl.TypeBuilder{ | ||||||
|  | 		File: protoimpl.DescBuilder{ | ||||||
|  | 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(), | ||||||
|  | 			RawDescriptor: file_app_stats_command_command_proto_rawDesc, | ||||||
|  | 			NumEnums:      0, | ||||||
|  | 			NumMessages:   8, | ||||||
|  | 			NumExtensions: 0, | ||||||
|  | 			NumServices:   1, | ||||||
|  | 		}, | ||||||
|  | 		GoTypes:           file_app_stats_command_command_proto_goTypes, | ||||||
|  | 		DependencyIndexes: file_app_stats_command_command_proto_depIdxs, | ||||||
|  | 		MessageInfos:      file_app_stats_command_command_proto_msgTypes, | ||||||
|  | 	}.Build() | ||||||
|  | 	File_app_stats_command_command_proto = out.File | ||||||
|  | 	file_app_stats_command_command_proto_rawDesc = nil | ||||||
|  | 	file_app_stats_command_command_proto_goTypes = nil | ||||||
|  | 	file_app_stats_command_command_proto_depIdxs = nil | ||||||
|  | } | ||||||
							
								
								
									
										55
									
								
								app/stats/command/command.proto
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								app/stats/command/command.proto
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | |||||||
|  | syntax = "proto3"; | ||||||
|  |  | ||||||
|  | package xray.app.stats.command; | ||||||
|  | option csharp_namespace = "Xray.App.Stats.Command"; | ||||||
|  | option go_package = "github.com/xtls/xray-core/v1/app/stats/command"; | ||||||
|  | option java_package = "com.xray.app.stats.command"; | ||||||
|  | option java_multiple_files = true; | ||||||
|  |  | ||||||
|  | message GetStatsRequest { | ||||||
|  |   // Name of the stat counter. | ||||||
|  |   string name = 1; | ||||||
|  |   // Whether or not to reset the counter to fetching its value. | ||||||
|  |   bool reset = 2; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message Stat { | ||||||
|  |   string name = 1; | ||||||
|  |   int64 value = 2; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message GetStatsResponse { | ||||||
|  |   Stat stat = 1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message QueryStatsRequest { | ||||||
|  |   string pattern = 1; | ||||||
|  |   bool reset = 2; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message QueryStatsResponse { | ||||||
|  |   repeated Stat stat = 1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message SysStatsRequest {} | ||||||
|  |  | ||||||
|  | message SysStatsResponse { | ||||||
|  |   uint32 NumGoroutine = 1; | ||||||
|  |   uint32 NumGC = 2; | ||||||
|  |   uint64 Alloc = 3; | ||||||
|  |   uint64 TotalAlloc = 4; | ||||||
|  |   uint64 Sys = 5; | ||||||
|  |   uint64 Mallocs = 6; | ||||||
|  |   uint64 Frees = 7; | ||||||
|  |   uint64 LiveObjects = 8; | ||||||
|  |   uint64 PauseTotalNs = 9; | ||||||
|  |   uint32 Uptime = 10; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | service StatsService { | ||||||
|  |   rpc GetStats(GetStatsRequest) returns (GetStatsResponse) {} | ||||||
|  |   rpc QueryStats(QueryStatsRequest) returns (QueryStatsResponse) {} | ||||||
|  |   rpc GetSysStats(SysStatsRequest) returns (SysStatsResponse) {} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message Config {} | ||||||
							
								
								
									
										169
									
								
								app/stats/command/command_grpc.pb.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										169
									
								
								app/stats/command/command_grpc.pb.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,169 @@ | |||||||
|  | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. | ||||||
|  |  | ||||||
|  | package command | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	context "context" | ||||||
|  | 	grpc "google.golang.org/grpc" | ||||||
|  | 	codes "google.golang.org/grpc/codes" | ||||||
|  | 	status "google.golang.org/grpc/status" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // This is a compile-time assertion to ensure that this generated file | ||||||
|  | // is compatible with the grpc package it is being compiled against. | ||||||
|  | const _ = grpc.SupportPackageIsVersion7 | ||||||
|  |  | ||||||
|  | // StatsServiceClient is the client API for StatsService service. | ||||||
|  | // | ||||||
|  | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. | ||||||
|  | type StatsServiceClient interface { | ||||||
|  | 	GetStats(ctx context.Context, in *GetStatsRequest, opts ...grpc.CallOption) (*GetStatsResponse, error) | ||||||
|  | 	QueryStats(ctx context.Context, in *QueryStatsRequest, opts ...grpc.CallOption) (*QueryStatsResponse, error) | ||||||
|  | 	GetSysStats(ctx context.Context, in *SysStatsRequest, opts ...grpc.CallOption) (*SysStatsResponse, error) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type statsServiceClient struct { | ||||||
|  | 	cc grpc.ClientConnInterface | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewStatsServiceClient(cc grpc.ClientConnInterface) StatsServiceClient { | ||||||
|  | 	return &statsServiceClient{cc} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *statsServiceClient) GetStats(ctx context.Context, in *GetStatsRequest, opts ...grpc.CallOption) (*GetStatsResponse, error) { | ||||||
|  | 	out := new(GetStatsResponse) | ||||||
|  | 	err := c.cc.Invoke(ctx, "/xray.app.stats.command.StatsService/GetStats", in, out, opts...) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return out, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *statsServiceClient) QueryStats(ctx context.Context, in *QueryStatsRequest, opts ...grpc.CallOption) (*QueryStatsResponse, error) { | ||||||
|  | 	out := new(QueryStatsResponse) | ||||||
|  | 	err := c.cc.Invoke(ctx, "/xray.app.stats.command.StatsService/QueryStats", in, out, opts...) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return out, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *statsServiceClient) GetSysStats(ctx context.Context, in *SysStatsRequest, opts ...grpc.CallOption) (*SysStatsResponse, error) { | ||||||
|  | 	out := new(SysStatsResponse) | ||||||
|  | 	err := c.cc.Invoke(ctx, "/xray.app.stats.command.StatsService/GetSysStats", in, out, opts...) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return out, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // StatsServiceServer is the server API for StatsService service. | ||||||
|  | // All implementations must embed UnimplementedStatsServiceServer | ||||||
|  | // for forward compatibility | ||||||
|  | type StatsServiceServer interface { | ||||||
|  | 	GetStats(context.Context, *GetStatsRequest) (*GetStatsResponse, error) | ||||||
|  | 	QueryStats(context.Context, *QueryStatsRequest) (*QueryStatsResponse, error) | ||||||
|  | 	GetSysStats(context.Context, *SysStatsRequest) (*SysStatsResponse, error) | ||||||
|  | 	mustEmbedUnimplementedStatsServiceServer() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // UnimplementedStatsServiceServer must be embedded to have forward compatible implementations. | ||||||
|  | type UnimplementedStatsServiceServer struct { | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (UnimplementedStatsServiceServer) GetStats(context.Context, *GetStatsRequest) (*GetStatsResponse, error) { | ||||||
|  | 	return nil, status.Errorf(codes.Unimplemented, "method GetStats not implemented") | ||||||
|  | } | ||||||
|  | func (UnimplementedStatsServiceServer) QueryStats(context.Context, *QueryStatsRequest) (*QueryStatsResponse, error) { | ||||||
|  | 	return nil, status.Errorf(codes.Unimplemented, "method QueryStats not implemented") | ||||||
|  | } | ||||||
|  | func (UnimplementedStatsServiceServer) GetSysStats(context.Context, *SysStatsRequest) (*SysStatsResponse, error) { | ||||||
|  | 	return nil, status.Errorf(codes.Unimplemented, "method GetSysStats not implemented") | ||||||
|  | } | ||||||
|  | func (UnimplementedStatsServiceServer) mustEmbedUnimplementedStatsServiceServer() {} | ||||||
|  |  | ||||||
|  | // UnsafeStatsServiceServer may be embedded to opt out of forward compatibility for this service. | ||||||
|  | // Use of this interface is not recommended, as added methods to StatsServiceServer will | ||||||
|  | // result in compilation errors. | ||||||
|  | type UnsafeStatsServiceServer interface { | ||||||
|  | 	mustEmbedUnimplementedStatsServiceServer() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func RegisterStatsServiceServer(s grpc.ServiceRegistrar, srv StatsServiceServer) { | ||||||
|  | 	s.RegisterService(&_StatsService_serviceDesc, srv) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func _StatsService_GetStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { | ||||||
|  | 	in := new(GetStatsRequest) | ||||||
|  | 	if err := dec(in); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	if interceptor == nil { | ||||||
|  | 		return srv.(StatsServiceServer).GetStats(ctx, in) | ||||||
|  | 	} | ||||||
|  | 	info := &grpc.UnaryServerInfo{ | ||||||
|  | 		Server:     srv, | ||||||
|  | 		FullMethod: "/xray.app.stats.command.StatsService/GetStats", | ||||||
|  | 	} | ||||||
|  | 	handler := func(ctx context.Context, req interface{}) (interface{}, error) { | ||||||
|  | 		return srv.(StatsServiceServer).GetStats(ctx, req.(*GetStatsRequest)) | ||||||
|  | 	} | ||||||
|  | 	return interceptor(ctx, in, info, handler) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func _StatsService_QueryStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { | ||||||
|  | 	in := new(QueryStatsRequest) | ||||||
|  | 	if err := dec(in); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	if interceptor == nil { | ||||||
|  | 		return srv.(StatsServiceServer).QueryStats(ctx, in) | ||||||
|  | 	} | ||||||
|  | 	info := &grpc.UnaryServerInfo{ | ||||||
|  | 		Server:     srv, | ||||||
|  | 		FullMethod: "/xray.app.stats.command.StatsService/QueryStats", | ||||||
|  | 	} | ||||||
|  | 	handler := func(ctx context.Context, req interface{}) (interface{}, error) { | ||||||
|  | 		return srv.(StatsServiceServer).QueryStats(ctx, req.(*QueryStatsRequest)) | ||||||
|  | 	} | ||||||
|  | 	return interceptor(ctx, in, info, handler) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func _StatsService_GetSysStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { | ||||||
|  | 	in := new(SysStatsRequest) | ||||||
|  | 	if err := dec(in); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	if interceptor == nil { | ||||||
|  | 		return srv.(StatsServiceServer).GetSysStats(ctx, in) | ||||||
|  | 	} | ||||||
|  | 	info := &grpc.UnaryServerInfo{ | ||||||
|  | 		Server:     srv, | ||||||
|  | 		FullMethod: "/xray.app.stats.command.StatsService/GetSysStats", | ||||||
|  | 	} | ||||||
|  | 	handler := func(ctx context.Context, req interface{}) (interface{}, error) { | ||||||
|  | 		return srv.(StatsServiceServer).GetSysStats(ctx, req.(*SysStatsRequest)) | ||||||
|  | 	} | ||||||
|  | 	return interceptor(ctx, in, info, handler) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var _StatsService_serviceDesc = grpc.ServiceDesc{ | ||||||
|  | 	ServiceName: "xray.app.stats.command.StatsService", | ||||||
|  | 	HandlerType: (*StatsServiceServer)(nil), | ||||||
|  | 	Methods: []grpc.MethodDesc{ | ||||||
|  | 		{ | ||||||
|  | 			MethodName: "GetStats", | ||||||
|  | 			Handler:    _StatsService_GetStats_Handler, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			MethodName: "QueryStats", | ||||||
|  | 			Handler:    _StatsService_QueryStats_Handler, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			MethodName: "GetSysStats", | ||||||
|  | 			Handler:    _StatsService_GetSysStats_Handler, | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | 	Streams:  []grpc.StreamDesc{}, | ||||||
|  | 	Metadata: "app/stats/command/command.proto", | ||||||
|  | } | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user
	 RPRX
					RPRX