mirror of
				https://github.com/optim-enterprises-bv/Xray-core.git
				synced 2025-10-30 18:18:04 +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 | ||||
| X-ray, Penetrate GFWs. The best v2ray-core, with XTLS support. Automatically patch and compile by GitHub Actions, fully compatible configuration. | ||||
| # Project X | ||||
|  | ||||
| [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