mirror of
https://github.com/XTLS/Xray-core.git
synced 2024-11-24 13:49:19 +02:00
v1.0.0
This commit is contained in:
parent
47d23e9972
commit
c7f7c08ead
|
@ -0,0 +1,373 @@
|
||||||
|
Mozilla Public License Version 2.0
|
||||||
|
==================================
|
||||||
|
|
||||||
|
1. Definitions
|
||||||
|
--------------
|
||||||
|
|
||||||
|
1.1. "Contributor"
|
||||||
|
means each individual or legal entity that creates, contributes to
|
||||||
|
the creation of, or owns Covered Software.
|
||||||
|
|
||||||
|
1.2. "Contributor Version"
|
||||||
|
means the combination of the Contributions of others (if any) used
|
||||||
|
by a Contributor and that particular Contributor's Contribution.
|
||||||
|
|
||||||
|
1.3. "Contribution"
|
||||||
|
means Covered Software of a particular Contributor.
|
||||||
|
|
||||||
|
1.4. "Covered Software"
|
||||||
|
means Source Code Form to which the initial Contributor has attached
|
||||||
|
the notice in Exhibit A, the Executable Form of such Source Code
|
||||||
|
Form, and Modifications of such Source Code Form, in each case
|
||||||
|
including portions thereof.
|
||||||
|
|
||||||
|
1.5. "Incompatible With Secondary Licenses"
|
||||||
|
means
|
||||||
|
|
||||||
|
(a) that the initial Contributor has attached the notice described
|
||||||
|
in Exhibit B to the Covered Software; or
|
||||||
|
|
||||||
|
(b) that the Covered Software was made available under the terms of
|
||||||
|
version 1.1 or earlier of the License, but not also under the
|
||||||
|
terms of a Secondary License.
|
||||||
|
|
||||||
|
1.6. "Executable Form"
|
||||||
|
means any form of the work other than Source Code Form.
|
||||||
|
|
||||||
|
1.7. "Larger Work"
|
||||||
|
means a work that combines Covered Software with other material, in
|
||||||
|
a separate file or files, that is not Covered Software.
|
||||||
|
|
||||||
|
1.8. "License"
|
||||||
|
means this document.
|
||||||
|
|
||||||
|
1.9. "Licensable"
|
||||||
|
means having the right to grant, to the maximum extent possible,
|
||||||
|
whether at the time of the initial grant or subsequently, any and
|
||||||
|
all of the rights conveyed by this License.
|
||||||
|
|
||||||
|
1.10. "Modifications"
|
||||||
|
means any of the following:
|
||||||
|
|
||||||
|
(a) any file in Source Code Form that results from an addition to,
|
||||||
|
deletion from, or modification of the contents of Covered
|
||||||
|
Software; or
|
||||||
|
|
||||||
|
(b) any new file in Source Code Form that contains any Covered
|
||||||
|
Software.
|
||||||
|
|
||||||
|
1.11. "Patent Claims" of a Contributor
|
||||||
|
means any patent claim(s), including without limitation, method,
|
||||||
|
process, and apparatus claims, in any patent Licensable by such
|
||||||
|
Contributor that would be infringed, but for the grant of the
|
||||||
|
License, by the making, using, selling, offering for sale, having
|
||||||
|
made, import, or transfer of either its Contributions or its
|
||||||
|
Contributor Version.
|
||||||
|
|
||||||
|
1.12. "Secondary License"
|
||||||
|
means either the GNU General Public License, Version 2.0, the GNU
|
||||||
|
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||||
|
Public License, Version 3.0, or any later versions of those
|
||||||
|
licenses.
|
||||||
|
|
||||||
|
1.13. "Source Code Form"
|
||||||
|
means the form of the work preferred for making modifications.
|
||||||
|
|
||||||
|
1.14. "You" (or "Your")
|
||||||
|
means an individual or a legal entity exercising rights under this
|
||||||
|
License. For legal entities, "You" includes any entity that
|
||||||
|
controls, is controlled by, or is under common control with You. For
|
||||||
|
purposes of this definition, "control" means (a) the power, direct
|
||||||
|
or indirect, to cause the direction or management of such entity,
|
||||||
|
whether by contract or otherwise, or (b) ownership of more than
|
||||||
|
fifty percent (50%) of the outstanding shares or beneficial
|
||||||
|
ownership of such entity.
|
||||||
|
|
||||||
|
2. License Grants and Conditions
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
2.1. Grants
|
||||||
|
|
||||||
|
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||||
|
non-exclusive license:
|
||||||
|
|
||||||
|
(a) under intellectual property rights (other than patent or trademark)
|
||||||
|
Licensable by such Contributor to use, reproduce, make available,
|
||||||
|
modify, display, perform, distribute, and otherwise exploit its
|
||||||
|
Contributions, either on an unmodified basis, with Modifications, or
|
||||||
|
as part of a Larger Work; and
|
||||||
|
|
||||||
|
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||||
|
for sale, have made, import, and otherwise transfer either its
|
||||||
|
Contributions or its Contributor Version.
|
||||||
|
|
||||||
|
2.2. Effective Date
|
||||||
|
|
||||||
|
The licenses granted in Section 2.1 with respect to any Contribution
|
||||||
|
become effective for each Contribution on the date the Contributor first
|
||||||
|
distributes such Contribution.
|
||||||
|
|
||||||
|
2.3. Limitations on Grant Scope
|
||||||
|
|
||||||
|
The licenses granted in this Section 2 are the only rights granted under
|
||||||
|
this License. No additional rights or licenses will be implied from the
|
||||||
|
distribution or licensing of Covered Software under this License.
|
||||||
|
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||||
|
Contributor:
|
||||||
|
|
||||||
|
(a) for any code that a Contributor has removed from Covered Software;
|
||||||
|
or
|
||||||
|
|
||||||
|
(b) for infringements caused by: (i) Your and any other third party's
|
||||||
|
modifications of Covered Software, or (ii) the combination of its
|
||||||
|
Contributions with other software (except as part of its Contributor
|
||||||
|
Version); or
|
||||||
|
|
||||||
|
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||||
|
its Contributions.
|
||||||
|
|
||||||
|
This License does not grant any rights in the trademarks, service marks,
|
||||||
|
or logos of any Contributor (except as may be necessary to comply with
|
||||||
|
the notice requirements in Section 3.4).
|
||||||
|
|
||||||
|
2.4. Subsequent Licenses
|
||||||
|
|
||||||
|
No Contributor makes additional grants as a result of Your choice to
|
||||||
|
distribute the Covered Software under a subsequent version of this
|
||||||
|
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||||
|
permitted under the terms of Section 3.3).
|
||||||
|
|
||||||
|
2.5. Representation
|
||||||
|
|
||||||
|
Each Contributor represents that the Contributor believes its
|
||||||
|
Contributions are its original creation(s) or it has sufficient rights
|
||||||
|
to grant the rights to its Contributions conveyed by this License.
|
||||||
|
|
||||||
|
2.6. Fair Use
|
||||||
|
|
||||||
|
This License is not intended to limit any rights You have under
|
||||||
|
applicable copyright doctrines of fair use, fair dealing, or other
|
||||||
|
equivalents.
|
||||||
|
|
||||||
|
2.7. Conditions
|
||||||
|
|
||||||
|
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||||
|
in Section 2.1.
|
||||||
|
|
||||||
|
3. Responsibilities
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
3.1. Distribution of Source Form
|
||||||
|
|
||||||
|
All distribution of Covered Software in Source Code Form, including any
|
||||||
|
Modifications that You create or to which You contribute, must be under
|
||||||
|
the terms of this License. You must inform recipients that the Source
|
||||||
|
Code Form of the Covered Software is governed by the terms of this
|
||||||
|
License, and how they can obtain a copy of this License. You may not
|
||||||
|
attempt to alter or restrict the recipients' rights in the Source Code
|
||||||
|
Form.
|
||||||
|
|
||||||
|
3.2. Distribution of Executable Form
|
||||||
|
|
||||||
|
If You distribute Covered Software in Executable Form then:
|
||||||
|
|
||||||
|
(a) such Covered Software must also be made available in Source Code
|
||||||
|
Form, as described in Section 3.1, and You must inform recipients of
|
||||||
|
the Executable Form how they can obtain a copy of such Source Code
|
||||||
|
Form by reasonable means in a timely manner, at a charge no more
|
||||||
|
than the cost of distribution to the recipient; and
|
||||||
|
|
||||||
|
(b) You may distribute such Executable Form under the terms of this
|
||||||
|
License, or sublicense it under different terms, provided that the
|
||||||
|
license for the Executable Form does not attempt to limit or alter
|
||||||
|
the recipients' rights in the Source Code Form under this License.
|
||||||
|
|
||||||
|
3.3. Distribution of a Larger Work
|
||||||
|
|
||||||
|
You may create and distribute a Larger Work under terms of Your choice,
|
||||||
|
provided that You also comply with the requirements of this License for
|
||||||
|
the Covered Software. If the Larger Work is a combination of Covered
|
||||||
|
Software with a work governed by one or more Secondary Licenses, and the
|
||||||
|
Covered Software is not Incompatible With Secondary Licenses, this
|
||||||
|
License permits You to additionally distribute such Covered Software
|
||||||
|
under the terms of such Secondary License(s), so that the recipient of
|
||||||
|
the Larger Work may, at their option, further distribute the Covered
|
||||||
|
Software under the terms of either this License or such Secondary
|
||||||
|
License(s).
|
||||||
|
|
||||||
|
3.4. Notices
|
||||||
|
|
||||||
|
You may not remove or alter the substance of any license notices
|
||||||
|
(including copyright notices, patent notices, disclaimers of warranty,
|
||||||
|
or limitations of liability) contained within the Source Code Form of
|
||||||
|
the Covered Software, except that You may alter any license notices to
|
||||||
|
the extent required to remedy known factual inaccuracies.
|
||||||
|
|
||||||
|
3.5. Application of Additional Terms
|
||||||
|
|
||||||
|
You may choose to offer, and to charge a fee for, warranty, support,
|
||||||
|
indemnity or liability obligations to one or more recipients of Covered
|
||||||
|
Software. However, You may do so only on Your own behalf, and not on
|
||||||
|
behalf of any Contributor. You must make it absolutely clear that any
|
||||||
|
such warranty, support, indemnity, or liability obligation is offered by
|
||||||
|
You alone, and You hereby agree to indemnify every Contributor for any
|
||||||
|
liability incurred by such Contributor as a result of warranty, support,
|
||||||
|
indemnity or liability terms You offer. You may include additional
|
||||||
|
disclaimers of warranty and limitations of liability specific to any
|
||||||
|
jurisdiction.
|
||||||
|
|
||||||
|
4. Inability to Comply Due to Statute or Regulation
|
||||||
|
---------------------------------------------------
|
||||||
|
|
||||||
|
If it is impossible for You to comply with any of the terms of this
|
||||||
|
License with respect to some or all of the Covered Software due to
|
||||||
|
statute, judicial order, or regulation then You must: (a) comply with
|
||||||
|
the terms of this License to the maximum extent possible; and (b)
|
||||||
|
describe the limitations and the code they affect. Such description must
|
||||||
|
be placed in a text file included with all distributions of the Covered
|
||||||
|
Software under this License. Except to the extent prohibited by statute
|
||||||
|
or regulation, such description must be sufficiently detailed for a
|
||||||
|
recipient of ordinary skill to be able to understand it.
|
||||||
|
|
||||||
|
5. Termination
|
||||||
|
--------------
|
||||||
|
|
||||||
|
5.1. The rights granted under this License will terminate automatically
|
||||||
|
if You fail to comply with any of its terms. However, if You become
|
||||||
|
compliant, then the rights granted under this License from a particular
|
||||||
|
Contributor are reinstated (a) provisionally, unless and until such
|
||||||
|
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||||
|
ongoing basis, if such Contributor fails to notify You of the
|
||||||
|
non-compliance by some reasonable means prior to 60 days after You have
|
||||||
|
come back into compliance. Moreover, Your grants from a particular
|
||||||
|
Contributor are reinstated on an ongoing basis if such Contributor
|
||||||
|
notifies You of the non-compliance by some reasonable means, this is the
|
||||||
|
first time You have received notice of non-compliance with this License
|
||||||
|
from such Contributor, and You become compliant prior to 30 days after
|
||||||
|
Your receipt of the notice.
|
||||||
|
|
||||||
|
5.2. If You initiate litigation against any entity by asserting a patent
|
||||||
|
infringement claim (excluding declaratory judgment actions,
|
||||||
|
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||||
|
directly or indirectly infringes any patent, then the rights granted to
|
||||||
|
You by any and all Contributors for the Covered Software under Section
|
||||||
|
2.1 of this License shall terminate.
|
||||||
|
|
||||||
|
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||||
|
end user license agreements (excluding distributors and resellers) which
|
||||||
|
have been validly granted by You or Your distributors under this License
|
||||||
|
prior to termination shall survive termination.
|
||||||
|
|
||||||
|
************************************************************************
|
||||||
|
* *
|
||||||
|
* 6. Disclaimer of Warranty *
|
||||||
|
* ------------------------- *
|
||||||
|
* *
|
||||||
|
* Covered Software is provided under this License on an "as is" *
|
||||||
|
* basis, without warranty of any kind, either expressed, implied, or *
|
||||||
|
* statutory, including, without limitation, warranties that the *
|
||||||
|
* Covered Software is free of defects, merchantable, fit for a *
|
||||||
|
* particular purpose or non-infringing. The entire risk as to the *
|
||||||
|
* quality and performance of the Covered Software is with You. *
|
||||||
|
* Should any Covered Software prove defective in any respect, You *
|
||||||
|
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||||
|
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||||
|
* essential part of this License. No use of any Covered Software is *
|
||||||
|
* authorized under this License except under this disclaimer. *
|
||||||
|
* *
|
||||||
|
************************************************************************
|
||||||
|
|
||||||
|
************************************************************************
|
||||||
|
* *
|
||||||
|
* 7. Limitation of Liability *
|
||||||
|
* -------------------------- *
|
||||||
|
* *
|
||||||
|
* Under no circumstances and under no legal theory, whether tort *
|
||||||
|
* (including negligence), contract, or otherwise, shall any *
|
||||||
|
* Contributor, or anyone who distributes Covered Software as *
|
||||||
|
* permitted above, be liable to You for any direct, indirect, *
|
||||||
|
* special, incidental, or consequential damages of any character *
|
||||||
|
* including, without limitation, damages for lost profits, loss of *
|
||||||
|
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||||
|
* and all other commercial damages or losses, even if such party *
|
||||||
|
* shall have been informed of the possibility of such damages. This *
|
||||||
|
* limitation of liability shall not apply to liability for death or *
|
||||||
|
* personal injury resulting from such party's negligence to the *
|
||||||
|
* extent applicable law prohibits such limitation. Some *
|
||||||
|
* jurisdictions do not allow the exclusion or limitation of *
|
||||||
|
* incidental or consequential damages, so this exclusion and *
|
||||||
|
* limitation may not apply to You. *
|
||||||
|
* *
|
||||||
|
************************************************************************
|
||||||
|
|
||||||
|
8. Litigation
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Any litigation relating to this License may be brought only in the
|
||||||
|
courts of a jurisdiction where the defendant maintains its principal
|
||||||
|
place of business and such litigation shall be governed by laws of that
|
||||||
|
jurisdiction, without reference to its conflict-of-law provisions.
|
||||||
|
Nothing in this Section shall prevent a party's ability to bring
|
||||||
|
cross-claims or counter-claims.
|
||||||
|
|
||||||
|
9. Miscellaneous
|
||||||
|
----------------
|
||||||
|
|
||||||
|
This License represents the complete agreement concerning the subject
|
||||||
|
matter hereof. If any provision of this License is held to be
|
||||||
|
unenforceable, such provision shall be reformed only to the extent
|
||||||
|
necessary to make it enforceable. Any law or regulation which provides
|
||||||
|
that the language of a contract shall be construed against the drafter
|
||||||
|
shall not be used to construe this License against a Contributor.
|
||||||
|
|
||||||
|
10. Versions of the License
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
10.1. New Versions
|
||||||
|
|
||||||
|
Mozilla Foundation is the license steward. Except as provided in Section
|
||||||
|
10.3, no one other than the license steward has the right to modify or
|
||||||
|
publish new versions of this License. Each version will be given a
|
||||||
|
distinguishing version number.
|
||||||
|
|
||||||
|
10.2. Effect of New Versions
|
||||||
|
|
||||||
|
You may distribute the Covered Software under the terms of the version
|
||||||
|
of the License under which You originally received the Covered Software,
|
||||||
|
or under the terms of any subsequent version published by the license
|
||||||
|
steward.
|
||||||
|
|
||||||
|
10.3. Modified Versions
|
||||||
|
|
||||||
|
If you create software not governed by this License, and you want to
|
||||||
|
create a new license for such software, you may create and use a
|
||||||
|
modified version of this License if you rename the license and remove
|
||||||
|
any references to the name of the license steward (except to note that
|
||||||
|
such modified license differs from this License).
|
||||||
|
|
||||||
|
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||||
|
Licenses
|
||||||
|
|
||||||
|
If You choose to distribute Source Code Form that is Incompatible With
|
||||||
|
Secondary Licenses under the terms of this version of the License, the
|
||||||
|
notice described in Exhibit B of this License must be attached.
|
||||||
|
|
||||||
|
Exhibit A - Source Code Form License Notice
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
If it is not possible or desirable to put the notice in a particular
|
||||||
|
file, then You may include the notice in a location (such as a LICENSE
|
||||||
|
file in a relevant directory) where a recipient would be likely to look
|
||||||
|
for such a notice.
|
||||||
|
|
||||||
|
You may add additional accurate notices of copyright ownership.
|
||||||
|
|
||||||
|
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||||
|
---------------------------------------------------------
|
||||||
|
|
||||||
|
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
|
defined by the Mozilla Public License, v. 2.0.
|
54
README.md
54
README.md
|
@ -1,2 +1,52 @@
|
||||||
# X-ray
|
# Project X
|
||||||
X-ray, Penetrate GFWs. The best v2ray-core, with XTLS support. Automatically patch and compile by GitHub Actions, fully compatible configuration.
|
|
||||||
|
[Project X](https://github.com/XTLS) originates from XTLS protocol, provides a set of network tools such as [Xray-core](https://github.com/XTLS/Xray-core) and [Xray-flutter](https://github.com/XTLS/Xray-flutter).
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
- Linux script
|
||||||
|
- [Xray-install](https://github.com/XTLS/Xray-install)
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
[Xray-examples](https://github.com/XTLS/Xray-examples) / [VLESS-TCP-XTLS-WHATEVER](https://github.com/XTLS/Xray-examples/tree/main/VLESS-TCP-XTLS-WHATEVER)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
[Mozilla Public License Version 2.0](https://github.com/XTLS/Xray-core/main/LICENSE)
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
This repo relies on the following third-party projects:
|
||||||
|
|
||||||
|
- Special thanks:
|
||||||
|
- [v2fly/v2ray-core](https://github.com/v2fly/v2ray-core)
|
||||||
|
- In production:
|
||||||
|
- [gorilla/websocket](https://github.com/gorilla/websocket)
|
||||||
|
- [lucas-clemente/quic-go](https://github.com/lucas-clemente/quic-go)
|
||||||
|
- [pires/go-proxyproto](https://github.com/pires/go-proxyproto)
|
||||||
|
- [seiflotfy/cuckoofilter](https://github.com/seiflotfy/cuckoofilter)
|
||||||
|
- [google/starlark-go](https://github.com/google/starlark-go)
|
||||||
|
- For testing only:
|
||||||
|
- [miekg/dns](https://github.com/miekg/dns)
|
||||||
|
- [h12w/socks](https://github.com/h12w/socks)
|
||||||
|
|
||||||
|
## Compilation
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
|
||||||
|
```
|
||||||
|
go build -o xray.exe -trimpath -ldflags "-s -w -buildid=" ./main
|
||||||
|
```
|
||||||
|
|
||||||
|
### Linux / macOS
|
||||||
|
|
||||||
|
```
|
||||||
|
go build -o xray -trimpath -ldflags "-s -w -buildid=" ./main
|
||||||
|
```
|
||||||
|
|
||||||
|
## Telegram
|
||||||
|
|
||||||
|
[Project X](https://t.me/projectXray)
|
||||||
|
|
||||||
|
[Project X Channel](https://t.me/projectXtls)
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
// Package app contains feature implementations of Xray. The features may be enabled during runtime.
|
||||||
|
package app
|
|
@ -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))
|
||||||
|
}))
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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 {}
|
|
@ -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{})
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
|
@ -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
|
||||||
|
}))
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
// +build !confonly
|
||||||
|
|
||||||
|
package dispatcher
|
||||||
|
|
||||||
|
//go:generate go run github.com/xtls/xray-core/v1/common/errors/errorgen
|
|
@ -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{})
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -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:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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{})
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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(),
|
||||||
|
}
|
||||||
|
}
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
|
@ -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))
|
||||||
|
}))
|
||||||
|
}
|
|
@ -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.")
|
||||||
|
}
|
||||||
|
}
|
|
@ -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:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}))
|
||||||
|
}
|
|
@ -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{}))
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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) {}
|
||||||
|
}
|
|
@ -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",
|
||||||
|
}
|
|
@ -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{})
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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{})
|
||||||
|
}
|
|
@ -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))
|
||||||
|
}))
|
||||||
|
}
|
|
@ -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
|
||||||
|
}))
|
||||||
|
}
|
|
@ -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())
|
||||||
|
}
|
|
@ -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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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{})
|
||||||
|
}
|
|
@ -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))
|
||||||
|
}))
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
@ -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
|
||||||
|
}))
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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 {}
|
|
@ -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",
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
package command
|
||||||
|
|
||||||
|
//go:generate go run github.com/xtls/xray-core/v1/common/errors/errorgen
|
|
@ -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{})
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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;
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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{})
|
||||||
|
}
|
|
@ -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))
|
||||||
|
}))
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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{})
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
|
@ -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))
|
||||||
|
}))
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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{})
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
|
@ -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...)
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}))
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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 {}
|
|
@ -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",
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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{})
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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
|
||||||
|
)
|
|
@ -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())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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;
|
||||||
|
}
|
|
@ -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{})
|
||||||
|
}
|
|
@ -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
|
||||||
|
}))
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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:
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}))
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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 {}
|
|
@ -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
Loading…
Reference in New Issue