mirror of
https://github.com/XTLS/Xray-core.git
synced 2025-08-22 17:46:48 +08:00
Compare commits
46 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a58e20c811 | ||
![]() |
2ab80d68ef | ||
![]() |
a208b07a73 | ||
![]() |
6b6974c804 | ||
![]() |
e286cdcaa8 | ||
![]() |
d77be80b40 | ||
![]() |
500c6de359 | ||
![]() |
a229a7f85e | ||
![]() |
cd4631ce99 | ||
![]() |
5e606169f1 | ||
![]() |
3f3b54f673 | ||
![]() |
575c7a9687 | ||
![]() |
bad397bf22 | ||
![]() |
e6711d1b48 | ||
![]() |
4bb61701b5 | ||
![]() |
ef4c63812b | ||
![]() |
e50f2af418 | ||
![]() |
3554886ce1 | ||
![]() |
a97d45c93a | ||
![]() |
ffa01e8dda | ||
![]() |
4abf98c1be | ||
![]() |
1ef824c0b4 | ||
![]() |
ed39fc3b79 | ||
![]() |
3b31189f13 | ||
![]() |
490e360c20 | ||
![]() |
32ae6d3952 | ||
![]() |
9f9059c7b1 | ||
![]() |
a149c78a4c | ||
![]() |
b0886027f5 | ||
![]() |
7033f7cf5f | ||
![]() |
ffc2f7c4e2 | ||
![]() |
ab927d2cca | ||
![]() |
0c0d878456 | ||
![]() |
24b637cd5e | ||
![]() |
f2cb13a8ec | ||
![]() |
dbcbb519e3 | ||
![]() |
8a5bf06925 | ||
![]() |
b0b2aaa70c | ||
![]() |
d111a046c0 | ||
![]() |
eaf30aa14a | ||
![]() |
42d158bd85 | ||
![]() |
00bcd40c34 | ||
![]() |
1adfc2720a | ||
![]() |
0f79126379 | ||
![]() |
7246001029 | ||
![]() |
5e6eff5ffa |
3
.github/build/friendly-filenames.json
vendored
3
.github/build/friendly-filenames.json
vendored
@@ -29,5 +29,6 @@
|
|||||||
"openbsd-arm7": { "friendlyName": "openbsd-arm32-v7a" },
|
"openbsd-arm7": { "friendlyName": "openbsd-arm32-v7a" },
|
||||||
"windows-386": { "friendlyName": "windows-32" },
|
"windows-386": { "friendlyName": "windows-32" },
|
||||||
"windows-amd64": { "friendlyName": "windows-64" },
|
"windows-amd64": { "friendlyName": "windows-64" },
|
||||||
|
"windows-arm64": { "friendlyName": "windows-arm64-v8a" },
|
||||||
"windows-arm7": { "friendlyName": "windows-arm32-v7a" }
|
"windows-arm7": { "friendlyName": "windows-arm32-v7a" }
|
||||||
}
|
}
|
11
.github/dependabot.yml
vendored
Normal file
11
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# To get started with Dependabot version updates, you'll need to specify which
|
||||||
|
# package ecosystems to update and where the package manifests are located.
|
||||||
|
# Please see the documentation for all configuration options:
|
||||||
|
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||||
|
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "gomod"
|
||||||
|
directory: "/" # Location of package manifests
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
@@ -52,7 +52,9 @@ jobs:
|
|||||||
- goos: android
|
- goos: android
|
||||||
goarch: arm64
|
goarch: arm64
|
||||||
# END Android ARM 8
|
# END Android ARM 8
|
||||||
# Windows ARM 7
|
# Windows ARM
|
||||||
|
- goos: windows
|
||||||
|
goarch: arm64
|
||||||
- goos: windows
|
- goos: windows
|
||||||
goarch: arm
|
goarch: arm
|
||||||
goarm: 7
|
goarm: 7
|
||||||
@@ -121,7 +123,7 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: ^1.17
|
go-version: ^1.17.2
|
||||||
|
|
||||||
- name: Get project dependencies
|
- name: Get project dependencies
|
||||||
run: go mod download
|
run: go mod download
|
||||||
|
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -28,7 +28,7 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: ^1.16
|
go-version: ^1.17.2
|
||||||
- name: Checkout codebase
|
- name: Checkout codebase
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
@@ -40,14 +40,16 @@
|
|||||||
- [luci-app-xray](https://github.com/yichya/luci-app-xray) ([openwrt-xray](https://github.com/yichya/openwrt-xray))
|
- [luci-app-xray](https://github.com/yichya/luci-app-xray) ([openwrt-xray](https://github.com/yichya/openwrt-xray))
|
||||||
- Windows
|
- Windows
|
||||||
- [v2rayN](https://github.com/2dust/v2rayN)
|
- [v2rayN](https://github.com/2dust/v2rayN)
|
||||||
- [Qv2ray](https://github.com/Qv2ray/Qv2ray)
|
- [Qv2ray](https://github.com/Qv2ray/Qv2ray) (This project had been archived and currently inactive)
|
||||||
- [Netch (NetFilter & TUN/TAP)](https://github.com/NetchX/Netch)
|
- [Netch (NetFilter & TUN/TAP)](https://github.com/NetchX/Netch)
|
||||||
- Android
|
- Android
|
||||||
- [v2rayNG](https://github.com/2dust/v2rayNG)
|
- [v2rayNG](https://github.com/2dust/v2rayNG)
|
||||||
- [AnXray](https://github.com/XTLS/AnXray)
|
- [AnXray](https://github.com/XTLS/AnXray)
|
||||||
- [Kitsunebi](https://github.com/rurirei/Kitsunebi/tree/release_xtls)
|
- [Kitsunebi](https://github.com/rurirei/Kitsunebi/tree/release_xtls)
|
||||||
- iOS / Mac
|
- iOS & macOS (with M1 chip)
|
||||||
- [Shadowrocket](https://apps.apple.com/app/shadowrocket/id932747118)
|
- [Shadowrocket](https://apps.apple.com/app/shadowrocket/id932747118)
|
||||||
|
- macOS (Intel chip & M1 chip)
|
||||||
|
- [Qv2ray](https://github.com/Qv2ray/Qv2ray) (This project had been archived and currently inactive)
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
|
@@ -1,13 +1,12 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.25.0
|
// protoc-gen-go v1.27.1
|
||||||
// protoc v3.14.0
|
// protoc v3.18.0
|
||||||
// source: app/commander/config.proto
|
// source: app/commander/config.proto
|
||||||
|
|
||||||
package commander
|
package commander
|
||||||
|
|
||||||
import (
|
import (
|
||||||
proto "github.com/golang/protobuf/proto"
|
|
||||||
serial "github.com/xtls/xray-core/common/serial"
|
serial "github.com/xtls/xray-core/common/serial"
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
@@ -22,10 +21,6 @@ const (
|
|||||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
_ = 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.
|
// Config is the settings for Commander.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
|
@@ -3,9 +3,10 @@ package commander
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/reflection"
|
"google.golang.org/grpc/reflection"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Service is a Commander service.
|
// Service is a Commander service.
|
||||||
|
@@ -1,13 +1,12 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.25.0
|
// protoc-gen-go v1.27.1
|
||||||
// protoc v3.14.0
|
// protoc v3.18.0
|
||||||
// source: app/dispatcher/config.proto
|
// source: app/dispatcher/config.proto
|
||||||
|
|
||||||
package dispatcher
|
package dispatcher
|
||||||
|
|
||||||
import (
|
import (
|
||||||
proto "github.com/golang/protobuf/proto"
|
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
@@ -21,10 +20,6 @@ const (
|
|||||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
_ = 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 {
|
type SessionConfig struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
|
@@ -25,9 +25,7 @@ import (
|
|||||||
"github.com/xtls/xray-core/transport/pipe"
|
"github.com/xtls/xray-core/transport/pipe"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var errSniffingTimeout = newError("timeout on sniffing")
|
||||||
errSniffingTimeout = newError("timeout on sniffing")
|
|
||||||
)
|
|
||||||
|
|
||||||
type cachedReader struct {
|
type cachedReader struct {
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
@@ -195,10 +193,15 @@ func shouldOverride(ctx context.Context, result SniffResult, request session.Sni
|
|||||||
if strings.HasPrefix(protocolString, p) {
|
if strings.HasPrefix(protocolString, p) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if fakeDNSEngine != nil && protocolString != "bittorrent" && p == "fakedns" &&
|
if fkr0, ok := fakeDNSEngine.(dns.FakeDNSEngineRev0); ok && protocolString != "bittorrent" && p == "fakedns" &&
|
||||||
destination.Address.Family().IsIP() && fakeDNSEngine.GetFakeIPRange().Contains(destination.Address.IP()) {
|
destination.Address.Family().IsIP() && fkr0.IsIPInIPPool(destination.Address) {
|
||||||
newError("Using sniffer ", protocolString, " since the fake DNS missed").WriteToLog(session.ExportIDToError(ctx))
|
newError("Using sniffer ", protocolString, " since the fake DNS missed").WriteToLog(session.ExportIDToError(ctx))
|
||||||
return true
|
return true
|
||||||
|
}
|
||||||
|
if resultSubset, ok := result.(SnifferIsProtoSubsetOf); ok {
|
||||||
|
if resultSubset.IsProtoSubsetOf(p) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,23 +312,14 @@ func sniffer(ctx context.Context, cReader *cachedReader, metadataOnly bool) (Sni
|
|||||||
func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.Link, destination net.Destination) {
|
func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.Link, destination net.Destination) {
|
||||||
var handler outbound.Handler
|
var handler outbound.Handler
|
||||||
|
|
||||||
skipRoutePick := false
|
if d.router != nil {
|
||||||
if content := session.ContentFromContext(ctx); content != nil {
|
if route, err := d.router.PickRoute(routing_session.AsRoutingContext(ctx)); err == nil {
|
||||||
skipRoutePick = content.SkipRoutePick
|
tag := route.GetOutboundTag()
|
||||||
}
|
if h := d.ohm.GetHandler(tag); h != nil {
|
||||||
|
newError("taking detour [", tag, "] for [", destination, "]").WriteToLog(session.ExportIDToError(ctx))
|
||||||
routingLink := routing_session.AsRoutingContext(ctx)
|
|
||||||
inTag := routingLink.GetInboundTag()
|
|
||||||
isPickRoute := false
|
|
||||||
if d.router != nil && !skipRoutePick {
|
|
||||||
if route, err := d.router.PickRoute(routingLink); err == nil {
|
|
||||||
outTag := route.GetOutboundTag()
|
|
||||||
isPickRoute = true
|
|
||||||
if h := d.ohm.GetHandler(outTag); h != nil {
|
|
||||||
newError("taking detour [", outTag, "] for [", destination, "]").WriteToLog(session.ExportIDToError(ctx))
|
|
||||||
handler = h
|
handler = h
|
||||||
} else {
|
} else {
|
||||||
newError("non existing outTag: ", outTag).AtWarning().WriteToLog(session.ExportIDToError(ctx))
|
newError("non existing outTag: ", tag).AtWarning().WriteToLog(session.ExportIDToError(ctx))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
newError("default route for ", destination).WriteToLog(session.ExportIDToError(ctx))
|
newError("default route for ", destination).WriteToLog(session.ExportIDToError(ctx))
|
||||||
@@ -345,19 +339,7 @@ func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.
|
|||||||
|
|
||||||
if accessMessage := log.AccessMessageFromContext(ctx); accessMessage != nil {
|
if accessMessage := log.AccessMessageFromContext(ctx); accessMessage != nil {
|
||||||
if tag := handler.Tag(); tag != "" {
|
if tag := handler.Tag(); tag != "" {
|
||||||
if isPickRoute {
|
accessMessage.Detour = tag
|
||||||
if inTag != "" {
|
|
||||||
accessMessage.Detour = inTag + " -> " + tag
|
|
||||||
} else {
|
|
||||||
accessMessage.Detour = tag
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if inTag != "" {
|
|
||||||
accessMessage.Detour = inTag + " >> " + tag
|
|
||||||
} else {
|
|
||||||
accessMessage.Detour = tag
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
log.Record(accessMessage)
|
log.Record(accessMessage)
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,7 @@ package dispatcher
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
@@ -32,6 +33,15 @@ func newFakeDNSSniffer(ctx context.Context) (protocolSnifferWithMetadata, error)
|
|||||||
return &fakeDNSSniffResult{domainName: domainFromFakeDNS}, nil
|
return &fakeDNSSniffResult{domainName: domainFromFakeDNS}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ipAddressInRangeValueI := ctx.Value(ipAddressInRange); ipAddressInRangeValueI != nil {
|
||||||
|
ipAddressInRangeValue := ipAddressInRangeValueI.(*ipAddressInRangeOpt)
|
||||||
|
if fkr0, ok := fakeDNSEngine.(dns.FakeDNSEngineRev0); ok {
|
||||||
|
inPool := fkr0.IsIPInIPPool(Target.Address)
|
||||||
|
ipAddressInRangeValue.addressInRange = &inPool
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil, common.ErrNoClue
|
return nil, common.ErrNoClue
|
||||||
}, metadataSniffer: true}, nil
|
}, metadataSniffer: true}, nil
|
||||||
}
|
}
|
||||||
@@ -47,3 +57,61 @@ func (fakeDNSSniffResult) Protocol() string {
|
|||||||
func (f fakeDNSSniffResult) Domain() string {
|
func (f fakeDNSSniffResult) Domain() string {
|
||||||
return f.domainName
|
return f.domainName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type fakeDNSExtraOpts int
|
||||||
|
|
||||||
|
const ipAddressInRange fakeDNSExtraOpts = 1
|
||||||
|
|
||||||
|
type ipAddressInRangeOpt struct {
|
||||||
|
addressInRange *bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type DNSThenOthersSniffResult struct {
|
||||||
|
domainName string
|
||||||
|
protocolOriginalName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f DNSThenOthersSniffResult) IsProtoSubsetOf(protocolName string) bool {
|
||||||
|
return strings.HasPrefix(protocolName, f.protocolOriginalName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (DNSThenOthersSniffResult) Protocol() string {
|
||||||
|
return "fakedns+others"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f DNSThenOthersSniffResult) Domain() string {
|
||||||
|
return f.domainName
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFakeDNSThenOthers(ctx context.Context, fakeDNSSniffer protocolSnifferWithMetadata, others []protocolSnifferWithMetadata) (
|
||||||
|
protocolSnifferWithMetadata, error) { // nolint: unparam
|
||||||
|
// ctx may be used in the future
|
||||||
|
_ = ctx
|
||||||
|
return protocolSnifferWithMetadata{
|
||||||
|
protocolSniffer: func(ctx context.Context, bytes []byte) (SniffResult, error) {
|
||||||
|
ipAddressInRangeValue := &ipAddressInRangeOpt{}
|
||||||
|
ctx = context.WithValue(ctx, ipAddressInRange, ipAddressInRangeValue)
|
||||||
|
result, err := fakeDNSSniffer.protocolSniffer(ctx, bytes)
|
||||||
|
if err == nil {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
if ipAddressInRangeValue.addressInRange != nil {
|
||||||
|
if *ipAddressInRangeValue.addressInRange {
|
||||||
|
for _, v := range others {
|
||||||
|
if v.metadataSniffer || bytes != nil {
|
||||||
|
if result, err := v.protocolSniffer(ctx, bytes); err == nil {
|
||||||
|
return DNSThenOthersSniffResult{domainName: result.Domain(), protocolOriginalName: result.Protocol()}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, common.ErrNoClue
|
||||||
|
}
|
||||||
|
newError("ip address not in fake dns range, return as is").AtDebug().WriteToLog()
|
||||||
|
return nil, common.ErrNoClue
|
||||||
|
}
|
||||||
|
newError("fake dns sniffer did not set address in range option, assume false.").AtWarning().WriteToLog()
|
||||||
|
return nil, common.ErrNoClue
|
||||||
|
},
|
||||||
|
metadataSniffer: false,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
@@ -37,7 +37,12 @@ func NewSniffer(ctx context.Context) *Sniffer {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
if sniffer, err := newFakeDNSSniffer(ctx); err == nil {
|
if sniffer, err := newFakeDNSSniffer(ctx); err == nil {
|
||||||
|
others := ret.sniffer
|
||||||
ret.sniffer = append(ret.sniffer, sniffer)
|
ret.sniffer = append(ret.sniffer, sniffer)
|
||||||
|
fakeDNSThenOthers, err := newFakeDNSThenOthers(ctx, sniffer, others)
|
||||||
|
if err == nil {
|
||||||
|
ret.sniffer = append([]protocolSnifferWithMetadata{fakeDNSThenOthers}, ret.sniffer...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
@@ -121,3 +126,7 @@ func (c compositeResult) ProtocolForDomainResult() string {
|
|||||||
type SnifferResultComposite interface {
|
type SnifferResultComposite interface {
|
||||||
ProtocolForDomainResult() string
|
ProtocolForDomainResult() string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SnifferIsProtoSubsetOf interface {
|
||||||
|
IsProtoSubsetOf(protocolName string) bool
|
||||||
|
}
|
||||||
|
63
app/dns/config.go
Normal file
63
app/dns/config.go
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/common/strmatcher"
|
||||||
|
"github.com/xtls/xray-core/common/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
var typeMap = map[DomainMatchingType]strmatcher.Type{
|
||||||
|
DomainMatchingType_Full: strmatcher.Full,
|
||||||
|
DomainMatchingType_Subdomain: strmatcher.Domain,
|
||||||
|
DomainMatchingType_Keyword: strmatcher.Substr,
|
||||||
|
DomainMatchingType_Regex: strmatcher.Regex,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
var 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"},
|
||||||
|
}
|
||||||
|
|
||||||
|
var localTLDsAndDotlessDomainsRule = &NameServer_OriginalRule{
|
||||||
|
Rule: "geosite:private",
|
||||||
|
Size: uint32(len(localTLDsAndDotlessDomains)),
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func toNetIP(addrs []net.Address) ([]net.IP, error) {
|
||||||
|
ips := make([]net.IP, 0, len(addrs))
|
||||||
|
for _, addr := range addrs {
|
||||||
|
if addr.Family().IsIP() {
|
||||||
|
ips = append(ips, addr.IP())
|
||||||
|
} else {
|
||||||
|
return nil, newError("Failed to convert address", addr, "to Net IP.").AtWarning()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ips, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateRandomTag() string {
|
||||||
|
id := uuid.New()
|
||||||
|
return "xray.system." + id.String()
|
||||||
|
}
|
@@ -1,13 +1,12 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.25.0
|
// protoc-gen-go v1.27.1
|
||||||
// protoc v3.14.0
|
// protoc v3.18.0
|
||||||
// source: app/dns/config.proto
|
// source: app/dns/config.proto
|
||||||
|
|
||||||
package dns
|
package dns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
proto "github.com/golang/protobuf/proto"
|
|
||||||
router "github.com/xtls/xray-core/app/router"
|
router "github.com/xtls/xray-core/app/router"
|
||||||
net "github.com/xtls/xray-core/common/net"
|
net "github.com/xtls/xray-core/common/net"
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
@@ -23,10 +22,6 @@ const (
|
|||||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
_ = 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
|
type DomainMatchingType int32
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -79,12 +74,63 @@ func (DomainMatchingType) EnumDescriptor() ([]byte, []int) {
|
|||||||
return file_app_dns_config_proto_rawDescGZIP(), []int{0}
|
return file_app_dns_config_proto_rawDescGZIP(), []int{0}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type QueryStrategy int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
QueryStrategy_USE_IP QueryStrategy = 0
|
||||||
|
QueryStrategy_USE_IP4 QueryStrategy = 1
|
||||||
|
QueryStrategy_USE_IP6 QueryStrategy = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
// Enum value maps for QueryStrategy.
|
||||||
|
var (
|
||||||
|
QueryStrategy_name = map[int32]string{
|
||||||
|
0: "USE_IP",
|
||||||
|
1: "USE_IP4",
|
||||||
|
2: "USE_IP6",
|
||||||
|
}
|
||||||
|
QueryStrategy_value = map[string]int32{
|
||||||
|
"USE_IP": 0,
|
||||||
|
"USE_IP4": 1,
|
||||||
|
"USE_IP6": 2,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (x QueryStrategy) Enum() *QueryStrategy {
|
||||||
|
p := new(QueryStrategy)
|
||||||
|
*p = x
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x QueryStrategy) String() string {
|
||||||
|
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (QueryStrategy) Descriptor() protoreflect.EnumDescriptor {
|
||||||
|
return file_app_dns_config_proto_enumTypes[1].Descriptor()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (QueryStrategy) Type() protoreflect.EnumType {
|
||||||
|
return &file_app_dns_config_proto_enumTypes[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x QueryStrategy) Number() protoreflect.EnumNumber {
|
||||||
|
return protoreflect.EnumNumber(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use QueryStrategy.Descriptor instead.
|
||||||
|
func (QueryStrategy) EnumDescriptor() ([]byte, []int) {
|
||||||
|
return file_app_dns_config_proto_rawDescGZIP(), []int{1}
|
||||||
|
}
|
||||||
|
|
||||||
type NameServer struct {
|
type NameServer struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
Address *net.Endpoint `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
|
Address *net.Endpoint `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
|
||||||
|
ClientIp []byte `protobuf:"bytes,5,opt,name=client_ip,json=clientIp,proto3" json:"client_ip,omitempty"`
|
||||||
|
SkipFallback bool `protobuf:"varint,6,opt,name=skipFallback,proto3" json:"skipFallback,omitempty"`
|
||||||
PrioritizedDomain []*NameServer_PriorityDomain `protobuf:"bytes,2,rep,name=prioritized_domain,json=prioritizedDomain,proto3" json:"prioritized_domain,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"`
|
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"`
|
OriginalRules []*NameServer_OriginalRule `protobuf:"bytes,4,rep,name=original_rules,json=originalRules,proto3" json:"original_rules,omitempty"`
|
||||||
@@ -129,6 +175,20 @@ func (x *NameServer) GetAddress() *net.Endpoint {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *NameServer) GetClientIp() []byte {
|
||||||
|
if x != nil {
|
||||||
|
return x.ClientIp
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NameServer) GetSkipFallback() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.SkipFallback
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (x *NameServer) GetPrioritizedDomain() []*NameServer_PriorityDomain {
|
func (x *NameServer) GetPrioritizedDomain() []*NameServer_PriorityDomain {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.PrioritizedDomain
|
return x.PrioritizedDomain
|
||||||
@@ -174,6 +234,11 @@ type Config struct {
|
|||||||
StaticHosts []*Config_HostMapping `protobuf:"bytes,4,rep,name=static_hosts,json=staticHosts,proto3" json:"static_hosts,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 is the inbound tag of DNS client.
|
||||||
Tag string `protobuf:"bytes,6,opt,name=tag,proto3" json:"tag,omitempty"`
|
Tag string `protobuf:"bytes,6,opt,name=tag,proto3" json:"tag,omitempty"`
|
||||||
|
// DisableCache disables DNS cache
|
||||||
|
DisableCache bool `protobuf:"varint,8,opt,name=disableCache,proto3" json:"disableCache,omitempty"`
|
||||||
|
QueryStrategy QueryStrategy `protobuf:"varint,9,opt,name=query_strategy,json=queryStrategy,proto3,enum=xray.app.dns.QueryStrategy" json:"query_strategy,omitempty"`
|
||||||
|
DisableFallback bool `protobuf:"varint,10,opt,name=disableFallback,proto3" json:"disableFallback,omitempty"`
|
||||||
|
DisableFallbackIfMatch bool `protobuf:"varint,11,opt,name=disableFallbackIfMatch,proto3" json:"disableFallbackIfMatch,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Config) Reset() {
|
func (x *Config) Reset() {
|
||||||
@@ -252,6 +317,34 @@ func (x *Config) GetTag() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetDisableCache() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.DisableCache
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetQueryStrategy() QueryStrategy {
|
||||||
|
if x != nil {
|
||||||
|
return x.QueryStrategy
|
||||||
|
}
|
||||||
|
return QueryStrategy_USE_IP
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetDisableFallback() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.DisableFallback
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetDisableFallbackIfMatch() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.DisableFallbackIfMatch
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
type NameServer_PriorityDomain struct {
|
type NameServer_PriorityDomain struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
@@ -371,8 +464,7 @@ type Config_HostMapping struct {
|
|||||||
Domain string `protobuf:"bytes,2,opt,name=domain,proto3" json:"domain,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"`
|
Ip [][]byte `protobuf:"bytes,3,rep,name=ip,proto3" json:"ip,omitempty"`
|
||||||
// ProxiedDomain indicates the mapped domain has the same IP address on this
|
// 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
|
// domain. Xray will use this domain for IP queries.
|
||||||
// effective if ip is empty.
|
|
||||||
ProxiedDomain string `protobuf:"bytes,4,opt,name=proxied_domain,json=proxiedDomain,proto3" json:"proxied_domain,omitempty"`
|
ProxiedDomain string `protobuf:"bytes,4,opt,name=proxied_domain,json=proxiedDomain,proto3" json:"proxied_domain,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -446,77 +538,98 @@ var file_app_dns_config_proto_rawDesc = []byte{
|
|||||||
0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x64, 0x65, 0x73, 0x74, 0x69,
|
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,
|
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, 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,
|
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xee, 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,
|
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,
|
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,
|
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,
|
0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69,
|
||||||
0x6f, 0x72, 0x69, 0x74, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18,
|
0x65, 0x6e, 0x74, 0x5f, 0x69, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x63, 0x6c,
|
||||||
0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70,
|
0x69, 0x65, 0x6e, 0x74, 0x49, 0x70, 0x12, 0x22, 0x0a, 0x0c, 0x73, 0x6b, 0x69, 0x70, 0x46, 0x61,
|
||||||
0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e,
|
0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x73, 0x6b,
|
||||||
0x50, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x11,
|
0x69, 0x70, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x56, 0x0a, 0x12, 0x70, 0x72,
|
||||||
0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x7a, 0x65, 0x64, 0x44, 0x6f, 0x6d, 0x61, 0x69,
|
0x69, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e,
|
||||||
0x6e, 0x12, 0x2c, 0x0a, 0x05, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b,
|
0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70,
|
||||||
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,
|
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,
|
0x2e, 0x50, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52,
|
||||||
0x48, 0x6f, 0x73, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x78, 0x72,
|
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, 0xef, 0x05, 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,
|
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,
|
0x67, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x52, 0x0b, 0x73,
|
||||||
0x52, 0x05, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e,
|
0x74, 0x61, 0x74, 0x69, 0x63, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61,
|
||||||
0x74, 0x5f, 0x69, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65,
|
0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x22, 0x0a, 0x0c,
|
||||||
0x6e, 0x74, 0x49, 0x70, 0x12, 0x43, 0x0a, 0x0c, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x5f, 0x68,
|
0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x61, 0x63, 0x68, 0x65, 0x18, 0x08, 0x20, 0x01,
|
||||||
0x6f, 0x73, 0x74, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61,
|
0x28, 0x08, 0x52, 0x0c, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x61, 0x63, 0x68, 0x65,
|
||||||
0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
|
0x12, 0x42, 0x0a, 0x0e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65,
|
||||||
0x2e, 0x48, 0x6f, 0x73, 0x74, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x52, 0x0b, 0x73, 0x74,
|
0x67, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
|
||||||
0x61, 0x74, 0x69, 0x63, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67,
|
0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x72,
|
||||||
0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x1a, 0x55, 0x0a, 0x0a, 0x48,
|
0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0d, 0x71, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x72, 0x61,
|
||||||
0x6f, 0x73, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79,
|
0x74, 0x65, 0x67, 0x79, 0x12, 0x28, 0x0a, 0x0f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x46,
|
||||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x31, 0x0a, 0x05, 0x76,
|
0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x64,
|
||||||
0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x78, 0x72, 0x61,
|
0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x36,
|
||||||
0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x49, 0x50, 0x4f,
|
0x0a, 0x16, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63,
|
||||||
0x72, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02,
|
0x6b, 0x49, 0x66, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x16,
|
||||||
0x38, 0x01, 0x1a, 0x92, 0x01, 0x0a, 0x0b, 0x48, 0x6f, 0x73, 0x74, 0x4d, 0x61, 0x70, 0x70, 0x69,
|
0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x49,
|
||||||
0x6e, 0x67, 0x12, 0x34, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e,
|
0x66, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x1a, 0x55, 0x0a, 0x0a, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x45,
|
||||||
0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e,
|
0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||||
0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x54, 0x79,
|
0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x31, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18,
|
||||||
0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61,
|
0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d,
|
||||||
0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e,
|
0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x49, 0x50, 0x4f, 0x72, 0x44, 0x6f, 0x6d, 0x61,
|
||||||
0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x70,
|
0x69, 0x6e, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x92, 0x01,
|
||||||
0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x78, 0x69, 0x65, 0x64, 0x5f, 0x64, 0x6f, 0x6d, 0x61,
|
0x0a, 0x0b, 0x48, 0x6f, 0x73, 0x74, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x12, 0x34, 0x0a,
|
||||||
0x69, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x78, 0x69, 0x65,
|
0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x78, 0x72,
|
||||||
0x64, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x2a, 0x45, 0x0a, 0x12, 0x44, 0x6f, 0x6d, 0x61, 0x69,
|
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, 0x12, 0x08, 0x0a,
|
0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74,
|
||||||
0x04, 0x46, 0x75, 0x6c, 0x6c, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x75, 0x62, 0x64, 0x6f,
|
0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20,
|
||||||
0x6d, 0x61, 0x69, 0x6e, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x4b, 0x65, 0x79, 0x77, 0x6f, 0x72,
|
0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69,
|
||||||
0x64, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x52, 0x65, 0x67, 0x65, 0x78, 0x10, 0x03, 0x42, 0x46,
|
0x70, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x70, 0x12, 0x25, 0x0a, 0x0e, 0x70,
|
||||||
0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64,
|
0x72, 0x6f, 0x78, 0x69, 0x65, 0x64, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x04, 0x20,
|
||||||
0x6e, 0x73, 0x50, 0x01, 0x5a, 0x21, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
|
0x01, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x78, 0x69, 0x65, 0x64, 0x44, 0x6f, 0x6d, 0x61,
|
||||||
0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f,
|
0x69, 0x6e, 0x4a, 0x04, 0x08, 0x07, 0x10, 0x08, 0x2a, 0x45, 0x0a, 0x12, 0x44, 0x6f, 0x6d, 0x61,
|
||||||
0x61, 0x70, 0x70, 0x2f, 0x64, 0x6e, 0x73, 0xaa, 0x02, 0x0c, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x41,
|
0x69, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x54, 0x79, 0x70, 0x65, 0x12, 0x08,
|
||||||
0x70, 0x70, 0x2e, 0x44, 0x6e, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
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, 0x2a,
|
||||||
|
0x35, 0x0a, 0x0d, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79,
|
||||||
|
0x12, 0x0a, 0x0a, 0x06, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07,
|
||||||
|
0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x34, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45,
|
||||||
|
0x5f, 0x49, 0x50, 0x36, 0x10, 0x02, 0x42, 0x46, 0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72,
|
||||||
|
0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x50, 0x01, 0x5a, 0x21, 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, 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 (
|
var (
|
||||||
@@ -531,37 +644,39 @@ func file_app_dns_config_proto_rawDescGZIP() []byte {
|
|||||||
return 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_enumTypes = make([]protoimpl.EnumInfo, 2)
|
||||||
var file_app_dns_config_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
|
var file_app_dns_config_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
|
||||||
var file_app_dns_config_proto_goTypes = []interface{}{
|
var file_app_dns_config_proto_goTypes = []interface{}{
|
||||||
(DomainMatchingType)(0), // 0: xray.app.dns.DomainMatchingType
|
(DomainMatchingType)(0), // 0: xray.app.dns.DomainMatchingType
|
||||||
(*NameServer)(nil), // 1: xray.app.dns.NameServer
|
(QueryStrategy)(0), // 1: xray.app.dns.QueryStrategy
|
||||||
(*Config)(nil), // 2: xray.app.dns.Config
|
(*NameServer)(nil), // 2: xray.app.dns.NameServer
|
||||||
(*NameServer_PriorityDomain)(nil), // 3: xray.app.dns.NameServer.PriorityDomain
|
(*Config)(nil), // 3: xray.app.dns.Config
|
||||||
(*NameServer_OriginalRule)(nil), // 4: xray.app.dns.NameServer.OriginalRule
|
(*NameServer_PriorityDomain)(nil), // 4: xray.app.dns.NameServer.PriorityDomain
|
||||||
nil, // 5: xray.app.dns.Config.HostsEntry
|
(*NameServer_OriginalRule)(nil), // 5: xray.app.dns.NameServer.OriginalRule
|
||||||
(*Config_HostMapping)(nil), // 6: xray.app.dns.Config.HostMapping
|
nil, // 6: xray.app.dns.Config.HostsEntry
|
||||||
(*net.Endpoint)(nil), // 7: xray.common.net.Endpoint
|
(*Config_HostMapping)(nil), // 7: xray.app.dns.Config.HostMapping
|
||||||
(*router.GeoIP)(nil), // 8: xray.app.router.GeoIP
|
(*net.Endpoint)(nil), // 8: xray.common.net.Endpoint
|
||||||
(*net.IPOrDomain)(nil), // 9: xray.common.net.IPOrDomain
|
(*router.GeoIP)(nil), // 9: xray.app.router.GeoIP
|
||||||
|
(*net.IPOrDomain)(nil), // 10: xray.common.net.IPOrDomain
|
||||||
}
|
}
|
||||||
var file_app_dns_config_proto_depIdxs = []int32{
|
var file_app_dns_config_proto_depIdxs = []int32{
|
||||||
7, // 0: xray.app.dns.NameServer.address:type_name -> xray.common.net.Endpoint
|
8, // 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
|
4, // 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
|
9, // 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
|
5, // 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
|
8, // 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
|
2, // 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, // 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
|
7, // 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
|
1, // 8: xray.app.dns.Config.query_strategy:type_name -> xray.app.dns.QueryStrategy
|
||||||
9, // 9: xray.app.dns.Config.HostsEntry.value:type_name -> xray.common.net.IPOrDomain
|
0, // 9: xray.app.dns.NameServer.PriorityDomain.type:type_name -> xray.app.dns.DomainMatchingType
|
||||||
0, // 10: xray.app.dns.Config.HostMapping.type:type_name -> xray.app.dns.DomainMatchingType
|
10, // 10: xray.app.dns.Config.HostsEntry.value:type_name -> xray.common.net.IPOrDomain
|
||||||
11, // [11:11] is the sub-list for method output_type
|
0, // 11: xray.app.dns.Config.HostMapping.type:type_name -> xray.app.dns.DomainMatchingType
|
||||||
11, // [11:11] is the sub-list for method input_type
|
12, // [12:12] is the sub-list for method output_type
|
||||||
11, // [11:11] is the sub-list for extension type_name
|
12, // [12:12] is the sub-list for method input_type
|
||||||
11, // [11:11] is the sub-list for extension extendee
|
12, // [12:12] is the sub-list for extension type_name
|
||||||
0, // [0:11] is the sub-list for field type_name
|
12, // [12:12] is the sub-list for extension extendee
|
||||||
|
0, // [0:12] is the sub-list for field type_name
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { file_app_dns_config_proto_init() }
|
func init() { file_app_dns_config_proto_init() }
|
||||||
@@ -636,7 +751,7 @@ func file_app_dns_config_proto_init() {
|
|||||||
File: protoimpl.DescBuilder{
|
File: protoimpl.DescBuilder{
|
||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: file_app_dns_config_proto_rawDesc,
|
RawDescriptor: file_app_dns_config_proto_rawDesc,
|
||||||
NumEnums: 1,
|
NumEnums: 2,
|
||||||
NumMessages: 6,
|
NumMessages: 6,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 0,
|
NumServices: 0,
|
||||||
|
@@ -12,6 +12,8 @@ import "app/router/config.proto";
|
|||||||
|
|
||||||
message NameServer {
|
message NameServer {
|
||||||
xray.common.net.Endpoint address = 1;
|
xray.common.net.Endpoint address = 1;
|
||||||
|
bytes client_ip = 5;
|
||||||
|
bool skipFallback = 6;
|
||||||
|
|
||||||
message PriorityDomain {
|
message PriorityDomain {
|
||||||
DomainMatchingType type = 1;
|
DomainMatchingType type = 1;
|
||||||
@@ -35,6 +37,12 @@ enum DomainMatchingType {
|
|||||||
Regex = 3;
|
Regex = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum QueryStrategy {
|
||||||
|
USE_IP = 0;
|
||||||
|
USE_IP4 = 1;
|
||||||
|
USE_IP6 = 2;
|
||||||
|
}
|
||||||
|
|
||||||
message Config {
|
message Config {
|
||||||
// Nameservers used by this DNS. Only traditional UDP servers are support at
|
// 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
|
// the moment. A special value 'localhost' as a domain address can be set to
|
||||||
@@ -59,8 +67,7 @@ message Config {
|
|||||||
repeated bytes ip = 3;
|
repeated bytes ip = 3;
|
||||||
|
|
||||||
// ProxiedDomain indicates the mapped domain has the same IP address on this
|
// 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
|
// domain. Xray will use this domain for IP queries.
|
||||||
// effective if ip is empty.
|
|
||||||
string proxied_domain = 4;
|
string proxied_domain = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,4 +77,12 @@ message Config {
|
|||||||
string tag = 6;
|
string tag = 6;
|
||||||
|
|
||||||
reserved 7;
|
reserved 7;
|
||||||
|
|
||||||
|
// DisableCache disables DNS cache
|
||||||
|
bool disableCache = 8;
|
||||||
|
|
||||||
|
QueryStrategy query_strategy = 9;
|
||||||
|
|
||||||
|
bool disableFallback = 10;
|
||||||
|
bool disableFallbackIfMatch = 11;
|
||||||
}
|
}
|
||||||
|
292
app/dns/dns.go
292
app/dns/dns.go
@@ -2,3 +2,295 @@
|
|||||||
package dns
|
package dns
|
||||||
|
|
||||||
//go:generate go run github.com/xtls/xray-core/common/errors/errorgen
|
//go:generate go run github.com/xtls/xray-core/common/errors/errorgen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/app/router"
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/common/session"
|
||||||
|
"github.com/xtls/xray-core/common/strmatcher"
|
||||||
|
"github.com/xtls/xray-core/features"
|
||||||
|
"github.com/xtls/xray-core/features/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DNS is a DNS rely server.
|
||||||
|
type DNS struct {
|
||||||
|
sync.Mutex
|
||||||
|
tag string
|
||||||
|
disableCache bool
|
||||||
|
disableFallback bool
|
||||||
|
disableFallbackIfMatch bool
|
||||||
|
ipOption *dns.IPOption
|
||||||
|
hosts *StaticHosts
|
||||||
|
clients []*Client
|
||||||
|
ctx context.Context
|
||||||
|
domainMatcher strmatcher.IndexMatcher
|
||||||
|
matcherInfos []*DomainMatcherInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// DomainMatcherInfo contains information attached to index returned by Server.domainMatcher
|
||||||
|
type DomainMatcherInfo struct {
|
||||||
|
clientIdx uint16
|
||||||
|
domainRuleIdx uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new DNS server with given configuration.
|
||||||
|
func New(ctx context.Context, config *Config) (*DNS, error) {
|
||||||
|
var tag string
|
||||||
|
if len(config.Tag) > 0 {
|
||||||
|
tag = config.Tag
|
||||||
|
} else {
|
||||||
|
tag = generateRandomTag()
|
||||||
|
}
|
||||||
|
|
||||||
|
var clientIP net.IP
|
||||||
|
switch len(config.ClientIp) {
|
||||||
|
case 0, net.IPv4len, net.IPv6len:
|
||||||
|
clientIP = net.IP(config.ClientIp)
|
||||||
|
default:
|
||||||
|
return nil, newError("unexpected client IP length ", len(config.ClientIp))
|
||||||
|
}
|
||||||
|
|
||||||
|
var ipOption *dns.IPOption
|
||||||
|
switch config.QueryStrategy {
|
||||||
|
case QueryStrategy_USE_IP:
|
||||||
|
ipOption = &dns.IPOption{
|
||||||
|
IPv4Enable: true,
|
||||||
|
IPv6Enable: true,
|
||||||
|
FakeEnable: false,
|
||||||
|
}
|
||||||
|
case QueryStrategy_USE_IP4:
|
||||||
|
ipOption = &dns.IPOption{
|
||||||
|
IPv4Enable: true,
|
||||||
|
IPv6Enable: false,
|
||||||
|
FakeEnable: false,
|
||||||
|
}
|
||||||
|
case QueryStrategy_USE_IP6:
|
||||||
|
ipOption = &dns.IPOption{
|
||||||
|
IPv4Enable: false,
|
||||||
|
IPv6Enable: true,
|
||||||
|
FakeEnable: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hosts, err := NewStaticHosts(config.StaticHosts, config.Hosts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, newError("failed to create hosts").Base(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
clients := []*Client{}
|
||||||
|
domainRuleCount := 0
|
||||||
|
for _, ns := range config.NameServer {
|
||||||
|
domainRuleCount += len(ns.PrioritizedDomain)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatcherInfos is ensured to cover the maximum index domainMatcher could return, where matcher's index starts from 1
|
||||||
|
matcherInfos := make([]*DomainMatcherInfo, domainRuleCount+1)
|
||||||
|
domainMatcher := &strmatcher.MatcherGroup{}
|
||||||
|
geoipContainer := router.GeoIPMatcherContainer{}
|
||||||
|
|
||||||
|
for _, endpoint := range config.NameServers {
|
||||||
|
features.PrintDeprecatedFeatureWarning("simple DNS server")
|
||||||
|
client, err := NewSimpleClient(ctx, endpoint, clientIP)
|
||||||
|
if err != nil {
|
||||||
|
return nil, newError("failed to create client").Base(err)
|
||||||
|
}
|
||||||
|
clients = append(clients, client)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ns := range config.NameServer {
|
||||||
|
clientIdx := len(clients)
|
||||||
|
updateDomain := func(domainRule strmatcher.Matcher, originalRuleIdx int, matcherInfos []*DomainMatcherInfo) error {
|
||||||
|
midx := domainMatcher.Add(domainRule)
|
||||||
|
matcherInfos[midx] = &DomainMatcherInfo{
|
||||||
|
clientIdx: uint16(clientIdx),
|
||||||
|
domainRuleIdx: uint16(originalRuleIdx),
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
myClientIP := clientIP
|
||||||
|
switch len(ns.ClientIp) {
|
||||||
|
case net.IPv4len, net.IPv6len:
|
||||||
|
myClientIP = net.IP(ns.ClientIp)
|
||||||
|
}
|
||||||
|
client, err := NewClient(ctx, ns, myClientIP, geoipContainer, &matcherInfos, updateDomain)
|
||||||
|
if err != nil {
|
||||||
|
return nil, newError("failed to create client").Base(err)
|
||||||
|
}
|
||||||
|
clients = append(clients, client)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is no DNS client in config, add a `localhost` DNS client
|
||||||
|
if len(clients) == 0 {
|
||||||
|
clients = append(clients, NewLocalDNSClient())
|
||||||
|
}
|
||||||
|
|
||||||
|
return &DNS{
|
||||||
|
tag: tag,
|
||||||
|
hosts: hosts,
|
||||||
|
ipOption: ipOption,
|
||||||
|
clients: clients,
|
||||||
|
ctx: ctx,
|
||||||
|
domainMatcher: domainMatcher,
|
||||||
|
matcherInfos: matcherInfos,
|
||||||
|
disableCache: config.DisableCache,
|
||||||
|
disableFallback: config.DisableFallback,
|
||||||
|
disableFallbackIfMatch: config.DisableFallbackIfMatch,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type implements common.HasType.
|
||||||
|
func (*DNS) Type() interface{} {
|
||||||
|
return dns.ClientType()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start implements common.Runnable.
|
||||||
|
func (s *DNS) Start() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements common.Closable.
|
||||||
|
func (s *DNS) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsOwnLink implements proxy.dns.ownLinkVerifier
|
||||||
|
func (s *DNS) IsOwnLink(ctx context.Context) bool {
|
||||||
|
inbound := session.InboundFromContext(ctx)
|
||||||
|
return inbound != nil && inbound.Tag == s.tag
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupIP implements dns.Client.
|
||||||
|
func (s *DNS) LookupIP(domain string, option dns.IPOption) ([]net.IP, error) {
|
||||||
|
if domain == "" {
|
||||||
|
return nil, newError("empty domain name")
|
||||||
|
}
|
||||||
|
|
||||||
|
option.IPv4Enable = option.IPv4Enable && s.ipOption.IPv4Enable
|
||||||
|
option.IPv6Enable = option.IPv6Enable && s.ipOption.IPv6Enable
|
||||||
|
|
||||||
|
if !option.IPv4Enable && !option.IPv6Enable {
|
||||||
|
return nil, dns.ErrEmptyResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize the FQDN form query
|
||||||
|
if strings.HasSuffix(domain, ".") {
|
||||||
|
domain = domain[:len(domain)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Static host lookup
|
||||||
|
switch addrs := s.hosts.Lookup(domain, option); {
|
||||||
|
case addrs == nil: // Domain not recorded in static host
|
||||||
|
break
|
||||||
|
case len(addrs) == 0: // Domain recorded, but no valid IP returned (e.g. IPv4 address with only IPv6 enabled)
|
||||||
|
return nil, dns.ErrEmptyResponse
|
||||||
|
case len(addrs) == 1 && addrs[0].Family().IsDomain(): // Domain replacement
|
||||||
|
newError("domain replaced: ", domain, " -> ", addrs[0].Domain()).WriteToLog()
|
||||||
|
domain = addrs[0].Domain()
|
||||||
|
default: // Successfully found ip records in static host
|
||||||
|
newError("returning ", len(addrs), " IP(s) for domain ", domain, " -> ", addrs).WriteToLog()
|
||||||
|
return toNetIP(addrs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name servers lookup
|
||||||
|
errs := []error{}
|
||||||
|
ctx := session.ContextWithInbound(s.ctx, &session.Inbound{Tag: s.tag})
|
||||||
|
for _, client := range s.sortClients(domain) {
|
||||||
|
if !option.FakeEnable && strings.EqualFold(client.Name(), "FakeDNS") {
|
||||||
|
newError("skip DNS resolution for domain ", domain, " at server ", client.Name()).AtDebug().WriteToLog()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ips, err := client.QueryIP(ctx, domain, option, s.disableCache)
|
||||||
|
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()
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
if err != context.Canceled && err != context.DeadlineExceeded && err != errExpectedIPNonMatch {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, newError("returning nil for domain ", domain).Base(errors.Combine(errs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetIPOption implements ClientWithIPOption.
|
||||||
|
func (s *DNS) GetIPOption() *dns.IPOption {
|
||||||
|
return s.ipOption
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetQueryOption implements ClientWithIPOption.
|
||||||
|
func (s *DNS) SetQueryOption(isIPv4Enable, isIPv6Enable bool) {
|
||||||
|
s.ipOption.IPv4Enable = isIPv4Enable
|
||||||
|
s.ipOption.IPv6Enable = isIPv6Enable
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFakeDNSOption implements ClientWithIPOption.
|
||||||
|
func (s *DNS) SetFakeDNSOption(isFakeEnable bool) {
|
||||||
|
s.ipOption.FakeEnable = isFakeEnable
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DNS) sortClients(domain string) []*Client {
|
||||||
|
clients := make([]*Client, 0, len(s.clients))
|
||||||
|
clientUsed := make([]bool, len(s.clients))
|
||||||
|
clientNames := make([]string, 0, len(s.clients))
|
||||||
|
domainRules := []string{}
|
||||||
|
|
||||||
|
// Priority domain matching
|
||||||
|
hasMatch := false
|
||||||
|
for _, match := range s.domainMatcher.Match(domain) {
|
||||||
|
info := s.matcherInfos[match]
|
||||||
|
client := s.clients[info.clientIdx]
|
||||||
|
domainRule := client.domains[info.domainRuleIdx]
|
||||||
|
domainRules = append(domainRules, fmt.Sprintf("%s(DNS idx:%d)", domainRule, info.clientIdx))
|
||||||
|
if clientUsed[info.clientIdx] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
clientUsed[info.clientIdx] = true
|
||||||
|
clients = append(clients, client)
|
||||||
|
clientNames = append(clientNames, client.Name())
|
||||||
|
hasMatch = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !(s.disableFallback || s.disableFallbackIfMatch && hasMatch) {
|
||||||
|
// Default round-robin query
|
||||||
|
for idx, client := range s.clients {
|
||||||
|
if clientUsed[idx] || client.skipFallback {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
clientUsed[idx] = true
|
||||||
|
clients = append(clients, client)
|
||||||
|
clientNames = append(clientNames, client.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(domainRules) > 0 {
|
||||||
|
newError("domain ", domain, " matches following rules: ", domainRules).AtDebug().WriteToLog()
|
||||||
|
}
|
||||||
|
if len(clientNames) > 0 {
|
||||||
|
newError("domain ", domain, " will use DNS in order: ", clientNames).AtDebug().WriteToLog()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(clients) == 0 {
|
||||||
|
clients = append(clients, s.clients[0])
|
||||||
|
clientNames = append(clientNames, s.clients[0].Name())
|
||||||
|
newError("domain ", domain, " will use the first DNS: ", clientNames).AtDebug().WriteToLog()
|
||||||
|
}
|
||||||
|
|
||||||
|
return clients
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
||||||
|
return New(ctx, config.(*Config))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
@@ -6,7 +6,6 @@ import (
|
|||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/app/dispatcher"
|
"github.com/xtls/xray-core/app/dispatcher"
|
||||||
. "github.com/xtls/xray-core/app/dns"
|
. "github.com/xtls/xray-core/app/dns"
|
||||||
"github.com/xtls/xray-core/app/policy"
|
"github.com/xtls/xray-core/app/policy"
|
||||||
@@ -22,8 +21,7 @@ import (
|
|||||||
"github.com/xtls/xray-core/testing/servers/udp"
|
"github.com/xtls/xray-core/testing/servers/udp"
|
||||||
)
|
)
|
||||||
|
|
||||||
type staticHandler struct {
|
type staticHandler struct{}
|
||||||
}
|
|
||||||
|
|
||||||
func (*staticHandler) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
|
func (*staticHandler) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
|
||||||
ans := new(dns.Msg)
|
ans := new(dns.Msg)
|
@@ -5,11 +5,12 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/dns/dnsmessage"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
dns_feature "github.com/xtls/xray-core/features/dns"
|
dns_feature "github.com/xtls/xray-core/features/dns"
|
||||||
"golang.org/x/net/dns/dnsmessage"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Fqdn normalize domain make sure it ends with '.'
|
// Fqdn normalize domain make sure it ends with '.'
|
||||||
@@ -53,9 +54,7 @@ func isNewer(baseRec *IPRecord, newRec *IPRecord) bool {
|
|||||||
return baseRec.Expire.Before(newRec.Expire)
|
return baseRec.Expire.Before(newRec.Expire)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var errRecordNotFound = errors.New("record not found")
|
||||||
errRecordNotFound = errors.New("record not found")
|
|
||||||
)
|
|
||||||
|
|
||||||
type dnsRequest struct {
|
type dnsRequest struct {
|
||||||
reqType dnsmessage.Type
|
reqType dnsmessage.Type
|
||||||
@@ -212,7 +211,7 @@ L:
|
|||||||
case dnsmessage.TypeAAAA:
|
case dnsmessage.TypeAAAA:
|
||||||
ans, err := parser.AAAAResource()
|
ans, err := parser.AAAAResource()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
newError("failed to parse A record for domain: ", ah.Name).Base(err).WriteToLog()
|
newError("failed to parse AAAA record for domain: ", ah.Name).Base(err).WriteToLog()
|
||||||
break L
|
break L
|
||||||
}
|
}
|
||||||
ipRecord.IP = append(ipRecord.IP, net.IPAddress(ans.AAAA[:]))
|
ipRecord.IP = append(ipRecord.IP, net.IPAddress(ans.AAAA[:]))
|
||||||
|
@@ -7,10 +7,11 @@ import (
|
|||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
|
"golang.org/x/net/dns/dnsmessage"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
dns_feature "github.com/xtls/xray-core/features/dns"
|
dns_feature "github.com/xtls/xray-core/features/dns"
|
||||||
"golang.org/x/net/dns/dnsmessage"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_parseResponse(t *testing.T) {
|
func Test_parseResponse(t *testing.T) {
|
||||||
@@ -49,20 +50,28 @@ func Test_parseResponse(t *testing.T) {
|
|||||||
want *IPRecord
|
want *IPRecord
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{"empty",
|
{
|
||||||
|
"empty",
|
||||||
&IPRecord{0, []net.Address(nil), time.Time{}, dnsmessage.RCodeSuccess},
|
&IPRecord{0, []net.Address(nil), time.Time{}, dnsmessage.RCodeSuccess},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{"error",
|
{
|
||||||
|
"error",
|
||||||
nil,
|
nil,
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
{"a record",
|
{
|
||||||
&IPRecord{1, []net.Address{net.ParseAddress("8.8.8.8"), net.ParseAddress("8.8.4.4")},
|
"a record",
|
||||||
time.Time{}, dnsmessage.RCodeSuccess},
|
&IPRecord{
|
||||||
|
1,
|
||||||
|
[]net.Address{net.ParseAddress("8.8.8.8"), net.ParseAddress("8.8.4.4")},
|
||||||
|
time.Time{},
|
||||||
|
dnsmessage.RCodeSuccess,
|
||||||
|
},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{"aaaa record",
|
{
|
||||||
|
"aaaa record",
|
||||||
&IPRecord{2, []net.Address{net.ParseAddress("2001::123:8888"), net.ParseAddress("2001::123:8844")}, time.Time{}, dnsmessage.RCodeSuccess},
|
&IPRecord{2, []net.Address{net.ParseAddress("2001::123:8888"), net.ParseAddress("2001::123:8844")}, time.Time{}, dnsmessage.RCodeSuccess},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
|
@@ -20,12 +20,30 @@ type Holder struct {
|
|||||||
config *FakeDnsPool
|
config *FakeDnsPool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fkdns *Holder) IsIPInIPPool(ip net.Address) bool {
|
||||||
|
if ip.Family().IsDomain() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return fkdns.ipRange.Contains(ip.IP())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fkdns *Holder) GetFakeIPForDomain3(domain string, ipv4, ipv6 bool) []net.Address {
|
||||||
|
isIPv6 := fkdns.ipRange.IP.To4() == nil
|
||||||
|
if (isIPv6 && ipv6) || (!isIPv6 && ipv4) {
|
||||||
|
return fkdns.GetFakeIPForDomain(domain)
|
||||||
|
}
|
||||||
|
return []net.Address{}
|
||||||
|
}
|
||||||
|
|
||||||
func (*Holder) Type() interface{} {
|
func (*Holder) Type() interface{} {
|
||||||
return (*dns.FakeDNSEngine)(nil)
|
return (*dns.FakeDNSEngine)(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fkdns *Holder) Start() error {
|
func (fkdns *Holder) Start() error {
|
||||||
return fkdns.initializeFromConfig()
|
if fkdns.config != nil && fkdns.config.IpPool != "" && fkdns.config.LruSize != 0 {
|
||||||
|
return fkdns.initializeFromConfig()
|
||||||
|
}
|
||||||
|
return newError("invalid fakeDNS setting")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fkdns *Holder) Close() error {
|
func (fkdns *Holder) Close() error {
|
||||||
@@ -41,7 +59,7 @@ func NewFakeDNSHolder() (*Holder, error) {
|
|||||||
if fkdns, err = NewFakeDNSHolderConfigOnly(nil); err != nil {
|
if fkdns, err = NewFakeDNSHolderConfigOnly(nil); err != nil {
|
||||||
return nil, newError("Unable to create Fake Dns Engine").Base(err).AtError()
|
return nil, newError("Unable to create Fake Dns Engine").Base(err).AtError()
|
||||||
}
|
}
|
||||||
err = fkdns.initialize(dns.FakeIPPool, 65535)
|
err = fkdns.initialize(dns.FakeIPv4Pool, 65535)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -79,13 +97,13 @@ func (fkdns *Holder) GetFakeIPForDomain(domain string) []net.Address {
|
|||||||
if v, ok := fkdns.domainToIP.Get(domain); ok {
|
if v, ok := fkdns.domainToIP.Get(domain); ok {
|
||||||
return []net.Address{v.(net.Address)}
|
return []net.Address{v.(net.Address)}
|
||||||
}
|
}
|
||||||
var currentTimeMillis = uint64(time.Now().UnixNano() / 1e6)
|
currentTimeMillis := uint64(time.Now().UnixNano() / 1e6)
|
||||||
ones, bits := fkdns.ipRange.Mask.Size()
|
ones, bits := fkdns.ipRange.Mask.Size()
|
||||||
rooms := bits - ones
|
rooms := bits - ones
|
||||||
if rooms < 64 {
|
if rooms < 64 {
|
||||||
currentTimeMillis %= (uint64(1) << rooms)
|
currentTimeMillis %= (uint64(1) << rooms)
|
||||||
}
|
}
|
||||||
var bigIntIP = big.NewInt(0).SetBytes(fkdns.ipRange.IP)
|
bigIntIP := big.NewInt(0).SetBytes(fkdns.ipRange.IP)
|
||||||
bigIntIP = bigIntIP.Add(bigIntIP, new(big.Int).SetUint64(currentTimeMillis))
|
bigIntIP = bigIntIP.Add(bigIntIP, new(big.Int).SetUint64(currentTimeMillis))
|
||||||
var ip net.Address
|
var ip net.Address
|
||||||
for {
|
for {
|
||||||
@@ -117,9 +135,92 @@ func (fkdns *Holder) GetDomainFromFakeDNS(ip net.Address) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFakeIPRange return fake IP range from configuration
|
type HolderMulti struct {
|
||||||
func (fkdns *Holder) GetFakeIPRange() *gonet.IPNet {
|
holders []*Holder
|
||||||
return fkdns.ipRange
|
|
||||||
|
config *FakeDnsPoolMulti
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HolderMulti) IsIPInIPPool(ip net.Address) bool {
|
||||||
|
if ip.Family().IsDomain() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, v := range h.holders {
|
||||||
|
if v.IsIPInIPPool(ip) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HolderMulti) GetFakeIPForDomain3(domain string, ipv4, ipv6 bool) []net.Address {
|
||||||
|
var ret []net.Address
|
||||||
|
for _, v := range h.holders {
|
||||||
|
ret = append(ret, v.GetFakeIPForDomain3(domain, ipv4, ipv6)...)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HolderMulti) GetFakeIPForDomain(domain string) []net.Address {
|
||||||
|
var ret []net.Address
|
||||||
|
for _, v := range h.holders {
|
||||||
|
ret = append(ret, v.GetFakeIPForDomain(domain)...)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HolderMulti) GetDomainFromFakeDNS(ip net.Address) string {
|
||||||
|
for _, v := range h.holders {
|
||||||
|
if domain := v.GetDomainFromFakeDNS(ip); domain != "" {
|
||||||
|
return domain
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HolderMulti) Type() interface{} {
|
||||||
|
return (*dns.FakeDNSEngine)(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HolderMulti) Start() error {
|
||||||
|
for _, v := range h.holders {
|
||||||
|
if v.config != nil && v.config.IpPool != "" && v.config.LruSize != 0 {
|
||||||
|
if err := v.Start(); err != nil {
|
||||||
|
return newError("Cannot start all fake dns pools").Base(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return newError("invalid fakeDNS setting")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HolderMulti) Close() error {
|
||||||
|
for _, v := range h.holders {
|
||||||
|
if err := v.Close(); err != nil {
|
||||||
|
return newError("Cannot close all fake dns pools").Base(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HolderMulti) createHolderGroups() error {
|
||||||
|
for _, v := range h.config.Pools {
|
||||||
|
holder, err := NewFakeDNSHolderConfigOnly(v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.holders = append(h.holders, holder)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFakeDNSHolderMulti(conf *FakeDnsPoolMulti) (*HolderMulti, error) {
|
||||||
|
holderMulti := &HolderMulti{nil, conf}
|
||||||
|
if err := holderMulti.createHolderGroups(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return holderMulti, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -131,4 +232,13 @@ func init() {
|
|||||||
}
|
}
|
||||||
return f, nil
|
return f, nil
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
common.Must(common.RegisterConfig((*FakeDnsPoolMulti)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
||||||
|
var f *HolderMulti
|
||||||
|
var err error
|
||||||
|
if f, err = NewFakeDNSHolderMulti(config.(*FakeDnsPoolMulti)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return f, nil
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
@@ -1,13 +1,12 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.25.0
|
// protoc-gen-go v1.27.1
|
||||||
// protoc v3.13.0
|
// protoc v3.18.0
|
||||||
// source: app/dns/fakedns/fakedns.proto
|
// source: app/dns/fakedns/fakedns.proto
|
||||||
|
|
||||||
package fakedns
|
package fakedns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
proto "github.com/golang/protobuf/proto"
|
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
@@ -21,10 +20,6 @@ const (
|
|||||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
_ = 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 FakeDnsPool struct {
|
type FakeDnsPool struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
@@ -80,24 +75,75 @@ func (x *FakeDnsPool) GetLruSize() int64 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FakeDnsPoolMulti struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Pools []*FakeDnsPool `protobuf:"bytes,1,rep,name=pools,proto3" json:"pools,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *FakeDnsPoolMulti) Reset() {
|
||||||
|
*x = FakeDnsPoolMulti{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_app_dns_fakedns_fakedns_proto_msgTypes[1]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *FakeDnsPoolMulti) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*FakeDnsPoolMulti) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *FakeDnsPoolMulti) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_app_dns_fakedns_fakedns_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 FakeDnsPoolMulti.ProtoReflect.Descriptor instead.
|
||||||
|
func (*FakeDnsPoolMulti) Descriptor() ([]byte, []int) {
|
||||||
|
return file_app_dns_fakedns_fakedns_proto_rawDescGZIP(), []int{1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *FakeDnsPoolMulti) GetPools() []*FakeDnsPool {
|
||||||
|
if x != nil {
|
||||||
|
return x.Pools
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var File_app_dns_fakedns_fakedns_proto protoreflect.FileDescriptor
|
var File_app_dns_fakedns_fakedns_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
var file_app_dns_fakedns_fakedns_proto_rawDesc = []byte{
|
var file_app_dns_fakedns_fakedns_proto_rawDesc = []byte{
|
||||||
0x0a, 0x1d, 0x61, 0x70, 0x70, 0x2f, 0x64, 0x6e, 0x73, 0x2f, 0x66, 0x61, 0x6b, 0x65, 0x64, 0x6e,
|
0x0a, 0x1d, 0x61, 0x70, 0x70, 0x2f, 0x64, 0x6e, 0x73, 0x2f, 0x66, 0x61, 0x6b, 0x65, 0x64, 0x6e,
|
||||||
0x73, 0x2f, 0x66, 0x61, 0x6b, 0x65, 0x64, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
|
0x73, 0x2f, 0x66, 0x61, 0x6b, 0x65, 0x64, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
|
||||||
0x1a, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e,
|
0x14, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x66, 0x61,
|
||||||
0x64, 0x6e, 0x73, 0x2e, 0x66, 0x61, 0x6b, 0x65, 0x64, 0x6e, 0x73, 0x22, 0x40, 0x0a, 0x0b, 0x46,
|
0x6b, 0x65, 0x64, 0x6e, 0x73, 0x22, 0x40, 0x0a, 0x0b, 0x46, 0x61, 0x6b, 0x65, 0x44, 0x6e, 0x73,
|
||||||
0x61, 0x6b, 0x65, 0x44, 0x6e, 0x73, 0x50, 0x6f, 0x6f, 0x6c, 0x12, 0x17, 0x0a, 0x07, 0x69, 0x70,
|
0x50, 0x6f, 0x6f, 0x6c, 0x12, 0x17, 0x0a, 0x07, 0x69, 0x70, 0x5f, 0x70, 0x6f, 0x6f, 0x6c, 0x18,
|
||||||
0x5f, 0x70, 0x6f, 0x6f, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x69, 0x70, 0x50,
|
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x69, 0x70, 0x50, 0x6f, 0x6f, 0x6c, 0x12, 0x18, 0x0a,
|
||||||
0x6f, 0x6f, 0x6c, 0x12, 0x18, 0x0a, 0x07, 0x6c, 0x72, 0x75, 0x53, 0x69, 0x7a, 0x65, 0x18, 0x02,
|
0x07, 0x6c, 0x72, 0x75, 0x53, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07,
|
||||||
0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x6c, 0x72, 0x75, 0x53, 0x69, 0x7a, 0x65, 0x42, 0x5f, 0x0a,
|
0x6c, 0x72, 0x75, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x4b, 0x0a, 0x10, 0x46, 0x61, 0x6b, 0x65, 0x44,
|
||||||
0x1e, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e,
|
0x6e, 0x73, 0x50, 0x6f, 0x6f, 0x6c, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x12, 0x37, 0x0a, 0x05, 0x70,
|
||||||
0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x66, 0x61, 0x6b, 0x65, 0x64, 0x6e, 0x73, 0x50,
|
0x6f, 0x6f, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x78, 0x72, 0x61,
|
||||||
0x01, 0x5a, 0x1e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72,
|
0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x66, 0x61, 0x6b, 0x65, 0x64, 0x6e,
|
||||||
0x65, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x64, 0x6e, 0x73, 0x2f, 0x66, 0x61, 0x6b, 0x65, 0x64, 0x6e,
|
0x73, 0x2e, 0x46, 0x61, 0x6b, 0x65, 0x44, 0x6e, 0x73, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x05, 0x70,
|
||||||
0x73, 0xaa, 0x02, 0x1a, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x41,
|
0x6f, 0x6f, 0x6c, 0x73, 0x42, 0x5e, 0x0a, 0x18, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79,
|
||||||
0x70, 0x70, 0x2e, 0x44, 0x6e, 0x73, 0x2e, 0x46, 0x61, 0x6b, 0x65, 0x64, 0x6e, 0x73, 0x62, 0x06,
|
0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x66, 0x61, 0x6b, 0x65, 0x64, 0x6e, 0x73,
|
||||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
0x50, 0x01, 0x5a, 0x29, 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, 0x61, 0x70,
|
||||||
|
0x70, 0x2f, 0x64, 0x6e, 0x73, 0x2f, 0x66, 0x61, 0x6b, 0x65, 0x64, 0x6e, 0x73, 0xaa, 0x02, 0x14,
|
||||||
|
0x58, 0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x44, 0x6e, 0x73, 0x2e, 0x46, 0x61, 0x6b,
|
||||||
|
0x65, 0x64, 0x6e, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -112,16 +158,18 @@ func file_app_dns_fakedns_fakedns_proto_rawDescGZIP() []byte {
|
|||||||
return file_app_dns_fakedns_fakedns_proto_rawDescData
|
return file_app_dns_fakedns_fakedns_proto_rawDescData
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_app_dns_fakedns_fakedns_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
var file_app_dns_fakedns_fakedns_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||||
var file_app_dns_fakedns_fakedns_proto_goTypes = []interface{}{
|
var file_app_dns_fakedns_fakedns_proto_goTypes = []interface{}{
|
||||||
(*FakeDnsPool)(nil), // 0: xray.app.dns.fakedns.FakeDnsPool
|
(*FakeDnsPool)(nil), // 0: xray.app.dns.fakedns.FakeDnsPool
|
||||||
|
(*FakeDnsPoolMulti)(nil), // 1: xray.app.dns.fakedns.FakeDnsPoolMulti
|
||||||
}
|
}
|
||||||
var file_app_dns_fakedns_fakedns_proto_depIdxs = []int32{
|
var file_app_dns_fakedns_fakedns_proto_depIdxs = []int32{
|
||||||
0, // [0:0] is the sub-list for method output_type
|
0, // 0: xray.app.dns.fakedns.FakeDnsPoolMulti.pools:type_name -> xray.app.dns.fakedns.FakeDnsPool
|
||||||
0, // [0:0] is the sub-list for method input_type
|
1, // [1:1] is the sub-list for method output_type
|
||||||
0, // [0:0] is the sub-list for extension type_name
|
1, // [1:1] is the sub-list for method input_type
|
||||||
0, // [0:0] is the sub-list for extension extendee
|
1, // [1:1] is the sub-list for extension type_name
|
||||||
0, // [0:0] is the sub-list for field 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_dns_fakedns_fakedns_proto_init() }
|
func init() { file_app_dns_fakedns_fakedns_proto_init() }
|
||||||
@@ -142,6 +190,18 @@ func file_app_dns_fakedns_fakedns_proto_init() {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
file_app_dns_fakedns_fakedns_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*FakeDnsPoolMulti); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
type x struct{}
|
type x struct{}
|
||||||
out := protoimpl.TypeBuilder{
|
out := protoimpl.TypeBuilder{
|
||||||
@@ -149,7 +209,7 @@ func file_app_dns_fakedns_fakedns_proto_init() {
|
|||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: file_app_dns_fakedns_fakedns_proto_rawDesc,
|
RawDescriptor: file_app_dns_fakedns_fakedns_proto_rawDesc,
|
||||||
NumEnums: 0,
|
NumEnums: 0,
|
||||||
NumMessages: 1,
|
NumMessages: 2,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 0,
|
NumServices: 0,
|
||||||
},
|
},
|
||||||
|
@@ -9,4 +9,8 @@ option java_multiple_files = true;
|
|||||||
message FakeDnsPool{
|
message FakeDnsPool{
|
||||||
string ip_pool = 1; //CIDR of IP pool used as fake DNS IP
|
string ip_pool = 1; //CIDR of IP pool used as fake DNS IP
|
||||||
int64 lruSize = 2; //Size of Pool for remembering relationship between domain name and IP address
|
int64 lruSize = 2; //Size of Pool for remembering relationship between domain name and IP address
|
||||||
|
}
|
||||||
|
|
||||||
|
message FakeDnsPoolMulti{
|
||||||
|
repeated FakeDnsPool pools = 1;
|
||||||
}
|
}
|
@@ -3,6 +3,8 @@ package fakedns
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
gonet "net"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
@@ -11,9 +13,7 @@ import (
|
|||||||
"github.com/xtls/xray-core/features/dns"
|
"github.com/xtls/xray-core/features/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var ipPrefix = "198.1"
|
||||||
ipPrefix = "198.18."
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNewFakeDnsHolder(_ *testing.T) {
|
func TestNewFakeDnsHolder(_ *testing.T) {
|
||||||
_, err := NewFakeDNSHolder()
|
_, err := NewFakeDNSHolder()
|
||||||
@@ -69,7 +69,7 @@ func TestFakeDnsHolderCreateMappingManySingleDomain(t *testing.T) {
|
|||||||
|
|
||||||
func TestFakeDnsHolderCreateMappingAndRollOver(t *testing.T) {
|
func TestFakeDnsHolderCreateMappingAndRollOver(t *testing.T) {
|
||||||
fkdns, err := NewFakeDNSHolderConfigOnly(&FakeDnsPool{
|
fkdns, err := NewFakeDNSHolderConfigOnly(&FakeDnsPool{
|
||||||
IpPool: dns.FakeIPPool,
|
IpPool: dns.FakeIPv4Pool,
|
||||||
LruSize: 256,
|
LruSize: 256,
|
||||||
})
|
})
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
@@ -103,3 +103,79 @@ func TestFakeDnsHolderCreateMappingAndRollOver(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFakeDNSMulti(t *testing.T) {
|
||||||
|
fakeMulti, err := NewFakeDNSHolderMulti(&FakeDnsPoolMulti{
|
||||||
|
Pools: []*FakeDnsPool{{
|
||||||
|
IpPool: "240.0.0.0/12",
|
||||||
|
LruSize: 256,
|
||||||
|
}, {
|
||||||
|
IpPool: "fddd:c5b4:ff5f:f4f0::/64",
|
||||||
|
LruSize: 256,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
common.Must(err)
|
||||||
|
|
||||||
|
err = fakeMulti.Start()
|
||||||
|
|
||||||
|
common.Must(err)
|
||||||
|
|
||||||
|
assert.Nil(t, err, "Should not throw error")
|
||||||
|
_ = fakeMulti
|
||||||
|
|
||||||
|
t.Run("checkInRange", func(t *testing.T) {
|
||||||
|
t.Run("ipv4", func(t *testing.T) {
|
||||||
|
inPool := fakeMulti.IsIPInIPPool(net.IPAddress([]byte{240, 0, 0, 5}))
|
||||||
|
assert.True(t, inPool)
|
||||||
|
})
|
||||||
|
t.Run("ipv6", func(t *testing.T) {
|
||||||
|
ip, err := gonet.ResolveIPAddr("ip", "fddd:c5b4:ff5f:f4f0::5")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
inPool := fakeMulti.IsIPInIPPool(net.IPAddress(ip.IP))
|
||||||
|
assert.True(t, inPool)
|
||||||
|
})
|
||||||
|
t.Run("ipv4_inverse", func(t *testing.T) {
|
||||||
|
inPool := fakeMulti.IsIPInIPPool(net.IPAddress([]byte{241, 0, 0, 5}))
|
||||||
|
assert.False(t, inPool)
|
||||||
|
})
|
||||||
|
t.Run("ipv6_inverse", func(t *testing.T) {
|
||||||
|
ip, err := gonet.ResolveIPAddr("ip", "fcdd:c5b4:ff5f:f4f0::5")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
inPool := fakeMulti.IsIPInIPPool(net.IPAddress(ip.IP))
|
||||||
|
assert.False(t, inPool)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("allocateTwoAddressForTwoPool", func(t *testing.T) {
|
||||||
|
address := fakeMulti.GetFakeIPForDomain("fakednstest.v2fly.org")
|
||||||
|
assert.Len(t, address, 2, "should be 2 address one for each pool")
|
||||||
|
t.Run("eachOfThemShouldResolve:0", func(t *testing.T) {
|
||||||
|
domain := fakeMulti.GetDomainFromFakeDNS(address[0])
|
||||||
|
assert.Equal(t, "fakednstest.v2fly.org", domain)
|
||||||
|
})
|
||||||
|
t.Run("eachOfThemShouldResolve:1", func(t *testing.T) {
|
||||||
|
domain := fakeMulti.GetDomainFromFakeDNS(address[1])
|
||||||
|
assert.Equal(t, "fakednstest.v2fly.org", domain)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("understandIPTypeSelector", func(t *testing.T) {
|
||||||
|
t.Run("ipv4", func(t *testing.T) {
|
||||||
|
address := fakeMulti.GetFakeIPForDomain3("fakednstestipv4.v2fly.org", true, false)
|
||||||
|
assert.Len(t, address, 1, "should be 1 address")
|
||||||
|
assert.True(t, address[0].Family().IsIPv4())
|
||||||
|
})
|
||||||
|
t.Run("ipv6", func(t *testing.T) {
|
||||||
|
address := fakeMulti.GetFakeIPForDomain3("fakednstestipv6.v2fly.org", false, true)
|
||||||
|
assert.Len(t, address, 1, "should be 1 address")
|
||||||
|
assert.True(t, address[0].Family().IsIPv6())
|
||||||
|
})
|
||||||
|
t.Run("ipv46", func(t *testing.T) {
|
||||||
|
address := fakeMulti.GetFakeIPForDomain3("fakednstestipv46.v2fly.org", true, true)
|
||||||
|
assert.Len(t, address, 2, "should be 2 address")
|
||||||
|
assert.True(t, address[0].Family().IsIPv4())
|
||||||
|
assert.True(t, address[1].Family().IsIPv6())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@@ -14,25 +14,6 @@ type StaticHosts struct {
|
|||||||
matchers *strmatcher.MatcherGroup
|
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.
|
// NewStaticHosts creates a new StaticHosts instance.
|
||||||
func NewStaticHosts(hosts []*Config_HostMapping, legacy map[string]*net.IPOrDomain) (*StaticHosts, error) {
|
func NewStaticHosts(hosts []*Config_HostMapping, legacy map[string]*net.IPOrDomain) (*StaticHosts, error) {
|
||||||
g := new(strmatcher.MatcherGroup)
|
g := new(strmatcher.MatcherGroup)
|
||||||
@@ -66,6 +47,8 @@ func NewStaticHosts(hosts []*Config_HostMapping, legacy map[string]*net.IPOrDoma
|
|||||||
id := g.Add(matcher)
|
id := g.Add(matcher)
|
||||||
ips := make([]net.Address, 0, len(mapping.Ip)+1)
|
ips := make([]net.Address, 0, len(mapping.Ip)+1)
|
||||||
switch {
|
switch {
|
||||||
|
case len(mapping.ProxiedDomain) > 0:
|
||||||
|
ips = append(ips, net.DomainAddress(mapping.ProxiedDomain))
|
||||||
case len(mapping.Ip) > 0:
|
case len(mapping.Ip) > 0:
|
||||||
for _, ip := range mapping.Ip {
|
for _, ip := range mapping.Ip {
|
||||||
addr := net.IPAddress(ip)
|
addr := net.IPAddress(ip)
|
||||||
@@ -74,19 +57,10 @@ func NewStaticHosts(hosts []*Config_HostMapping, legacy map[string]*net.IPOrDoma
|
|||||||
}
|
}
|
||||||
ips = append(ips, addr)
|
ips = append(ips, addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
case len(mapping.ProxiedDomain) > 0:
|
|
||||||
ips = append(ips, net.DomainAddress(mapping.ProxiedDomain))
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, newError("neither IP address nor proxied domain specified for domain: ", mapping.Domain).AtWarning()
|
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
|
sh.ips[id] = ips
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,24 +74,36 @@ func filterIP(ips []net.Address, option dns.IPOption) []net.Address {
|
|||||||
filtered = append(filtered, ip)
|
filtered = append(filtered, ip)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(filtered) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return filtered
|
return filtered
|
||||||
}
|
}
|
||||||
|
|
||||||
// LookupIP returns IP address for the given domain, if exists in this StaticHosts.
|
func (h *StaticHosts) lookupInternal(domain string) []net.Address {
|
||||||
func (h *StaticHosts) LookupIP(domain string, option dns.IPOption) []net.Address {
|
var ips []net.Address
|
||||||
indices := h.matchers.Match(domain)
|
for _, id := range h.matchers.Match(domain) {
|
||||||
if len(indices) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
ips := []net.Address{}
|
|
||||||
for _, id := range indices {
|
|
||||||
ips = append(ips, h.ips[id]...)
|
ips = append(ips, h.ips[id]...)
|
||||||
}
|
}
|
||||||
if len(ips) == 1 && ips[0].Family().IsDomain() {
|
return ips
|
||||||
return ips
|
}
|
||||||
}
|
|
||||||
return filterIP(ips, option)
|
func (h *StaticHosts) lookup(domain string, option dns.IPOption, maxDepth int) []net.Address {
|
||||||
|
switch addrs := h.lookupInternal(domain); {
|
||||||
|
case len(addrs) == 0: // Not recorded in static hosts, return nil
|
||||||
|
return nil
|
||||||
|
case len(addrs) == 1 && addrs[0].Family().IsDomain(): // Try to unwrap domain
|
||||||
|
newError("found replaced domain: ", domain, " -> ", addrs[0].Domain(), ". Try to unwrap it").AtDebug().WriteToLog()
|
||||||
|
if maxDepth > 0 {
|
||||||
|
unwrapped := h.lookup(addrs[0].Domain(), option, maxDepth-1)
|
||||||
|
if unwrapped != nil {
|
||||||
|
return unwrapped
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return addrs
|
||||||
|
default: // IP record found, return a non-nil IP array
|
||||||
|
return filterIP(addrs, option)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup returns IP addresses or proxied domain for the given domain, if exists in this StaticHosts.
|
||||||
|
func (h *StaticHosts) Lookup(domain string, option dns.IPOption) []net.Address {
|
||||||
|
return h.lookup(domain, option, 5)
|
||||||
}
|
}
|
||||||
|
@@ -4,7 +4,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
|
|
||||||
. "github.com/xtls/xray-core/app/dns"
|
. "github.com/xtls/xray-core/app/dns"
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
@@ -20,6 +19,20 @@ func TestStaticHosts(t *testing.T) {
|
|||||||
{1, 1, 1, 1},
|
{1, 1, 1, 1},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Type: DomainMatchingType_Full,
|
||||||
|
Domain: "proxy.xray.com",
|
||||||
|
Ip: [][]byte{
|
||||||
|
{1, 2, 3, 4},
|
||||||
|
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
|
||||||
|
},
|
||||||
|
ProxiedDomain: "another-proxy.xray.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: DomainMatchingType_Full,
|
||||||
|
Domain: "proxy2.xray.com",
|
||||||
|
ProxiedDomain: "proxy.xray.com",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Type: DomainMatchingType_Subdomain,
|
Type: DomainMatchingType_Subdomain,
|
||||||
Domain: "example.cn",
|
Domain: "example.cn",
|
||||||
@@ -32,6 +45,7 @@ func TestStaticHosts(t *testing.T) {
|
|||||||
Domain: "baidu.com",
|
Domain: "baidu.com",
|
||||||
Ip: [][]byte{
|
Ip: [][]byte{
|
||||||
{127, 0, 0, 1},
|
{127, 0, 0, 1},
|
||||||
|
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -40,7 +54,7 @@ func TestStaticHosts(t *testing.T) {
|
|||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
{
|
{
|
||||||
ips := hosts.LookupIP("example.com", dns.IPOption{
|
ips := hosts.Lookup("example.com", dns.IPOption{
|
||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
})
|
})
|
||||||
@@ -53,7 +67,33 @@ func TestStaticHosts(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
ips := hosts.LookupIP("www.example.cn", dns.IPOption{
|
domain := hosts.Lookup("proxy.xray.com", dns.IPOption{
|
||||||
|
IPv4Enable: true,
|
||||||
|
IPv6Enable: false,
|
||||||
|
})
|
||||||
|
if len(domain) != 1 {
|
||||||
|
t.Error("expect 1 domain, but got ", len(domain))
|
||||||
|
}
|
||||||
|
if diff := cmp.Diff(domain[0].Domain(), "another-proxy.xray.com"); diff != "" {
|
||||||
|
t.Error(diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
domain := hosts.Lookup("proxy2.xray.com", dns.IPOption{
|
||||||
|
IPv4Enable: true,
|
||||||
|
IPv6Enable: false,
|
||||||
|
})
|
||||||
|
if len(domain) != 1 {
|
||||||
|
t.Error("expect 1 domain, but got ", len(domain))
|
||||||
|
}
|
||||||
|
if diff := cmp.Diff(domain[0].Domain(), "another-proxy.xray.com"); diff != "" {
|
||||||
|
t.Error(diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
ips := hosts.Lookup("www.example.cn", dns.IPOption{
|
||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
})
|
})
|
||||||
@@ -66,7 +106,7 @@ func TestStaticHosts(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
ips := hosts.LookupIP("baidu.com", dns.IPOption{
|
ips := hosts.Lookup("baidu.com", dns.IPOption{
|
||||||
IPv4Enable: false,
|
IPv4Enable: false,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
})
|
})
|
||||||
|
@@ -2,40 +2,219 @@ package dns
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/app/router"
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/common/strmatcher"
|
||||||
|
"github.com/xtls/xray-core/core"
|
||||||
"github.com/xtls/xray-core/features/dns"
|
"github.com/xtls/xray-core/features/dns"
|
||||||
"github.com/xtls/xray-core/features/dns/localdns"
|
"github.com/xtls/xray-core/features/routing"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Client is the interface for DNS client.
|
// Server is the interface for Name Server.
|
||||||
type Client interface {
|
type Server interface {
|
||||||
// Name of the Client.
|
// Name of the Client.
|
||||||
Name() string
|
Name() string
|
||||||
|
|
||||||
// QueryIP sends IP queries to its configured server.
|
// QueryIP sends IP queries to its configured server.
|
||||||
QueryIP(ctx context.Context, domain string, option dns.IPOption) ([]net.IP, error)
|
QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns.IPOption, disableCache bool) ([]net.IP, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type LocalNameServer struct {
|
// Client is the interface for DNS client.
|
||||||
client *localdns.Client
|
type Client struct {
|
||||||
|
server Server
|
||||||
|
clientIP net.IP
|
||||||
|
skipFallback bool
|
||||||
|
domains []string
|
||||||
|
expectIPs []*router.GeoIPMatcher
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *LocalNameServer) QueryIP(_ context.Context, domain string, option dns.IPOption) ([]net.IP, error) {
|
var errExpectedIPNonMatch = errors.New("expectIPs not match")
|
||||||
if option.IPv4Enable || option.IPv6Enable {
|
|
||||||
return s.client.LookupIP(domain, option)
|
// NewServer creates a name server object according to the network destination url.
|
||||||
|
func NewServer(dest net.Destination, dispatcher routing.Dispatcher) (Server, error) {
|
||||||
|
if address := dest.Address; address.Family().IsDomain() {
|
||||||
|
u, err := url.Parse(address.Domain())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case strings.EqualFold(u.String(), "localhost"):
|
||||||
|
return NewLocalNameServer(), nil
|
||||||
|
case strings.EqualFold(u.Scheme, "https"): // DOH Remote mode
|
||||||
|
return NewDoHNameServer(u, dispatcher)
|
||||||
|
case strings.EqualFold(u.Scheme, "https+local"): // DOH Local mode
|
||||||
|
return NewDoHLocalNameServer(u), nil
|
||||||
|
case strings.EqualFold(u.Scheme, "quic+local"): // DNS-over-QUIC Local mode
|
||||||
|
return NewQUICNameServer(u)
|
||||||
|
case strings.EqualFold(u.Scheme, "tcp"): // DNS-over-TCP Remote mode
|
||||||
|
return NewTCPNameServer(u, dispatcher)
|
||||||
|
case strings.EqualFold(u.Scheme, "tcp+local"): // DNS-over-TCP Local mode
|
||||||
|
return NewTCPLocalNameServer(u)
|
||||||
|
case strings.EqualFold(u.String(), "fakedns"):
|
||||||
|
return NewFakeDNSServer(), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if dest.Network == net.Network_Unknown {
|
||||||
|
dest.Network = net.Network_UDP
|
||||||
|
}
|
||||||
|
if dest.Network == net.Network_UDP { // UDP classic DNS mode
|
||||||
|
return NewClassicNameServer(dest, dispatcher), nil
|
||||||
|
}
|
||||||
|
return nil, newError("No available name server could be created from ", dest).AtWarning()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient creates a DNS client managing a name server with client IP, domain rules and expected IPs.
|
||||||
|
func NewClient(ctx context.Context, ns *NameServer, clientIP net.IP, container router.GeoIPMatcherContainer, matcherInfos *[]*DomainMatcherInfo, updateDomainRule func(strmatcher.Matcher, int, []*DomainMatcherInfo) error) (*Client, error) {
|
||||||
|
client := &Client{}
|
||||||
|
|
||||||
|
err := core.RequireFeatures(ctx, func(dispatcher routing.Dispatcher) error {
|
||||||
|
// Create a new server for each client for now
|
||||||
|
server, err := NewServer(ns.Address.AsDestination(), dispatcher)
|
||||||
|
if err != nil {
|
||||||
|
return newError("failed to create nameserver").Base(err).AtWarning()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Priotize local domains with specific TLDs or without any dot to local DNS
|
||||||
|
if _, isLocalDNS := server.(*LocalNameServer); isLocalDNS {
|
||||||
|
ns.PrioritizedDomain = append(ns.PrioritizedDomain, localTLDsAndDotlessDomains...)
|
||||||
|
ns.OriginalRules = append(ns.OriginalRules, localTLDsAndDotlessDomainsRule)
|
||||||
|
// The following lines is a solution to avoid core panics(rule index out of range) when setting `localhost` DNS client in config.
|
||||||
|
// Because the `localhost` DNS client will apend len(localTLDsAndDotlessDomains) rules into matcherInfos to match `geosite:private` default rule.
|
||||||
|
// But `matcherInfos` has no enough length to add rules, which leads to core panics (rule index out of range).
|
||||||
|
// To avoid this, the length of `matcherInfos` must be equal to the expected, so manually append it with Golang default zero value first for later modification.
|
||||||
|
// Related issues:
|
||||||
|
// https://github.com/v2fly/v2ray-core/issues/529
|
||||||
|
// https://github.com/v2fly/v2ray-core/issues/719
|
||||||
|
for i := 0; i < len(localTLDsAndDotlessDomains); i++ {
|
||||||
|
*matcherInfos = append(*matcherInfos, &DomainMatcherInfo{
|
||||||
|
clientIdx: uint16(0),
|
||||||
|
domainRuleIdx: uint16(0),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Establish domain rules
|
||||||
|
var rules []string
|
||||||
|
ruleCurr := 0
|
||||||
|
ruleIter := 0
|
||||||
|
for _, domain := range ns.PrioritizedDomain {
|
||||||
|
domainRule, err := toStrMatcher(domain.Type, domain.Domain)
|
||||||
|
if err != nil {
|
||||||
|
return newError("failed to create prioritized domain").Base(err).AtWarning()
|
||||||
|
}
|
||||||
|
originalRuleIdx := ruleCurr
|
||||||
|
if ruleCurr < len(ns.OriginalRules) {
|
||||||
|
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)
|
||||||
|
rules = append(rules, domainRule.String())
|
||||||
|
ruleCurr++
|
||||||
|
}
|
||||||
|
err = updateDomainRule(domainRule, originalRuleIdx, *matcherInfos)
|
||||||
|
if err != nil {
|
||||||
|
return newError("failed to create prioritized domain").Base(err).AtWarning()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Establish expected IPs
|
||||||
|
var matchers []*router.GeoIPMatcher
|
||||||
|
for _, geoip := range ns.Geoip {
|
||||||
|
matcher, err := container.Add(geoip)
|
||||||
|
if err != nil {
|
||||||
|
return newError("failed to create ip matcher").Base(err).AtWarning()
|
||||||
|
}
|
||||||
|
matchers = append(matchers, matcher)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(clientIP) > 0 {
|
||||||
|
switch ns.Address.Address.GetAddress().(type) {
|
||||||
|
case *net.IPOrDomain_Domain:
|
||||||
|
newError("DNS: client ", ns.Address.Address.GetDomain(), " uses clientIP ", clientIP.String()).AtInfo().WriteToLog()
|
||||||
|
case *net.IPOrDomain_Ip:
|
||||||
|
newError("DNS: client ", ns.Address.Address.GetIp(), " uses clientIP ", clientIP.String()).AtInfo().WriteToLog()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client.server = server
|
||||||
|
client.clientIP = clientIP
|
||||||
|
client.skipFallback = ns.SkipFallback
|
||||||
|
client.domains = rules
|
||||||
|
client.expectIPs = matchers
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return client, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSimpleClient creates a DNS client with a simple destination.
|
||||||
|
func NewSimpleClient(ctx context.Context, endpoint *net.Endpoint, clientIP net.IP) (*Client, error) {
|
||||||
|
client := &Client{}
|
||||||
|
err := core.RequireFeatures(ctx, func(dispatcher routing.Dispatcher) error {
|
||||||
|
server, err := NewServer(endpoint.AsDestination(), dispatcher)
|
||||||
|
if err != nil {
|
||||||
|
return newError("failed to create nameserver").Base(err).AtWarning()
|
||||||
|
}
|
||||||
|
client.server = server
|
||||||
|
client.clientIP = clientIP
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if len(clientIP) > 0 {
|
||||||
|
switch endpoint.Address.GetAddress().(type) {
|
||||||
|
case *net.IPOrDomain_Domain:
|
||||||
|
newError("DNS: client ", endpoint.Address.GetDomain(), " uses clientIP ", clientIP.String()).AtInfo().WriteToLog()
|
||||||
|
case *net.IPOrDomain_Ip:
|
||||||
|
newError("DNS: client ", endpoint.Address.GetIp(), " uses clientIP ", clientIP.String()).AtInfo().WriteToLog()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, newError("neither IPv4 nor IPv6 is enabled")
|
return client, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *LocalNameServer) Name() string {
|
// Name returns the server name the client manages.
|
||||||
return "localhost"
|
func (c *Client) Name() string {
|
||||||
|
return c.server.Name()
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLocalNameServer() *LocalNameServer {
|
// QueryIP send DNS query to the name server with the client's IP.
|
||||||
newError("DNS: created localhost client").AtInfo().WriteToLog()
|
func (c *Client) QueryIP(ctx context.Context, domain string, option dns.IPOption, disableCache bool) ([]net.IP, error) {
|
||||||
return &LocalNameServer{
|
ctx, cancel := context.WithTimeout(ctx, 4*time.Second)
|
||||||
client: localdns.New(),
|
ips, err := c.server.QueryIP(ctx, domain, c.clientIP, option, disableCache)
|
||||||
|
cancel()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return ips, err
|
||||||
}
|
}
|
||||||
|
return c.MatchExpectedIPs(domain, ips)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchExpectedIPs matches queried domain IPs with expected IPs and returns matched ones.
|
||||||
|
func (c *Client) MatchExpectedIPs(domain string, ips []net.IP) ([]net.IP, error) {
|
||||||
|
if len(c.expectIPs) == 0 {
|
||||||
|
return ips, nil
|
||||||
|
}
|
||||||
|
newIps := []net.IP{}
|
||||||
|
for _, ip := range ips {
|
||||||
|
for _, matcher := range c.expectIPs {
|
||||||
|
if matcher.Match(ip) {
|
||||||
|
newIps = append(newIps, ip)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(newIps) == 0 {
|
||||||
|
return nil, errExpectedIPNonMatch
|
||||||
|
}
|
||||||
|
newError("domain ", domain, " expectIPs ", newIps, " matched at server ", c.Name()).AtDebug().WriteToLog()
|
||||||
|
return newIps, nil
|
||||||
}
|
}
|
||||||
|
@@ -5,13 +5,14 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/dns/dnsmessage"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/log"
|
"github.com/xtls/xray-core/common/log"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
@@ -23,7 +24,6 @@ import (
|
|||||||
dns_feature "github.com/xtls/xray-core/features/dns"
|
dns_feature "github.com/xtls/xray-core/features/dns"
|
||||||
"github.com/xtls/xray-core/features/routing"
|
"github.com/xtls/xray-core/features/routing"
|
||||||
"github.com/xtls/xray-core/transport/internet"
|
"github.com/xtls/xray-core/transport/internet"
|
||||||
"golang.org/x/net/dns/dnsmessage"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// DoHNameServer implemented DNS over HTTPS (RFC8484) Wire Format,
|
// DoHNameServer implemented DNS over HTTPS (RFC8484) Wire Format,
|
||||||
@@ -32,20 +32,19 @@ import (
|
|||||||
type DoHNameServer struct {
|
type DoHNameServer struct {
|
||||||
dispatcher routing.Dispatcher
|
dispatcher routing.Dispatcher
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
ips map[string]record
|
ips map[string]*record
|
||||||
pub *pubsub.Service
|
pub *pubsub.Service
|
||||||
cleanup *task.Periodic
|
cleanup *task.Periodic
|
||||||
reqID uint32
|
reqID uint32
|
||||||
clientIP net.IP
|
|
||||||
httpClient *http.Client
|
httpClient *http.Client
|
||||||
dohURL string
|
dohURL string
|
||||||
name string
|
name string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDoHNameServer creates DOH client object for remote resolving
|
// NewDoHNameServer creates DOH server object for remote resolving.
|
||||||
func NewDoHNameServer(url *url.URL, dispatcher routing.Dispatcher, clientIP net.IP) (*DoHNameServer, error) {
|
func NewDoHNameServer(url *url.URL, dispatcher routing.Dispatcher) (*DoHNameServer, error) {
|
||||||
newError("DNS: created Remote DOH client for ", url.String()).AtInfo().WriteToLog()
|
newError("DNS: created Remote DOH client for ", url.String()).AtInfo().WriteToLog()
|
||||||
s := baseDOHNameServer(url, "DOH", clientIP)
|
s := baseDOHNameServer(url, "DOH")
|
||||||
|
|
||||||
s.dispatcher = dispatcher
|
s.dispatcher = dispatcher
|
||||||
tr := &http.Transport{
|
tr := &http.Transport{
|
||||||
@@ -104,9 +103,9 @@ func NewDoHNameServer(url *url.URL, dispatcher routing.Dispatcher, clientIP net.
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewDoHLocalNameServer creates DOH client object for local resolving
|
// NewDoHLocalNameServer creates DOH client object for local resolving
|
||||||
func NewDoHLocalNameServer(url *url.URL, clientIP net.IP) *DoHNameServer {
|
func NewDoHLocalNameServer(url *url.URL) *DoHNameServer {
|
||||||
url.Scheme = "https"
|
url.Scheme = "https"
|
||||||
s := baseDOHNameServer(url, "DOHL", clientIP)
|
s := baseDOHNameServer(url, "DOHL")
|
||||||
tr := &http.Transport{
|
tr := &http.Transport{
|
||||||
IdleConnTimeout: 90 * time.Second,
|
IdleConnTimeout: 90 * time.Second,
|
||||||
ForceAttemptHTTP2: true,
|
ForceAttemptHTTP2: true,
|
||||||
@@ -136,23 +135,21 @@ func NewDoHLocalNameServer(url *url.URL, clientIP net.IP) *DoHNameServer {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func baseDOHNameServer(url *url.URL, prefix string, clientIP net.IP) *DoHNameServer {
|
func baseDOHNameServer(url *url.URL, prefix string) *DoHNameServer {
|
||||||
s := &DoHNameServer{
|
s := &DoHNameServer{
|
||||||
ips: make(map[string]record),
|
ips: make(map[string]*record),
|
||||||
clientIP: clientIP,
|
pub: pubsub.NewService(),
|
||||||
pub: pubsub.NewService(),
|
name: prefix + "//" + url.Host,
|
||||||
name: prefix + "//" + url.Host,
|
dohURL: url.String(),
|
||||||
dohURL: url.String(),
|
|
||||||
}
|
}
|
||||||
s.cleanup = &task.Periodic{
|
s.cleanup = &task.Periodic{
|
||||||
Interval: time.Minute,
|
Interval: time.Minute,
|
||||||
Execute: s.Cleanup,
|
Execute: s.Cleanup,
|
||||||
}
|
}
|
||||||
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name returns client name
|
// Name implements Server.
|
||||||
func (s *DoHNameServer) Name() string {
|
func (s *DoHNameServer) Name() string {
|
||||||
return s.name
|
return s.name
|
||||||
}
|
}
|
||||||
@@ -184,7 +181,7 @@ func (s *DoHNameServer) Cleanup() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(s.ips) == 0 {
|
if len(s.ips) == 0 {
|
||||||
s.ips = make(map[string]record)
|
s.ips = make(map[string]*record)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -194,7 +191,10 @@ func (s *DoHNameServer) updateIP(req *dnsRequest, ipRec *IPRecord) {
|
|||||||
elapsed := time.Since(req.start)
|
elapsed := time.Since(req.start)
|
||||||
|
|
||||||
s.Lock()
|
s.Lock()
|
||||||
rec := s.ips[req.domain]
|
rec, found := s.ips[req.domain]
|
||||||
|
if !found {
|
||||||
|
rec = &record{}
|
||||||
|
}
|
||||||
updated := false
|
updated := false
|
||||||
|
|
||||||
switch req.reqType {
|
switch req.reqType {
|
||||||
@@ -204,7 +204,7 @@ func (s *DoHNameServer) updateIP(req *dnsRequest, ipRec *IPRecord) {
|
|||||||
updated = true
|
updated = true
|
||||||
}
|
}
|
||||||
case dnsmessage.TypeAAAA:
|
case dnsmessage.TypeAAAA:
|
||||||
addr := make([]net.Address, 0)
|
addr := make([]net.Address, 0, len(ipRec.IP))
|
||||||
for _, ip := range ipRec.IP {
|
for _, ip := range ipRec.IP {
|
||||||
if len(ip.IP()) == net.IPv6len {
|
if len(ip.IP()) == net.IPv6len {
|
||||||
addr = append(addr, ip)
|
addr = append(addr, ip)
|
||||||
@@ -235,7 +235,7 @@ func (s *DoHNameServer) newReqID() uint16 {
|
|||||||
return uint16(atomic.AddUint32(&s.reqID, 1))
|
return uint16(atomic.AddUint32(&s.reqID, 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DoHNameServer) sendQuery(ctx context.Context, domain string, option dns_feature.IPOption) {
|
func (s *DoHNameServer) sendQuery(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption) {
|
||||||
newError(s.name, " querying: ", domain).AtInfo().WriteToLog(session.ExportIDToError(ctx))
|
newError(s.name, " querying: ", domain).AtInfo().WriteToLog(session.ExportIDToError(ctx))
|
||||||
|
|
||||||
if s.name+"." == "DOH//"+domain {
|
if s.name+"." == "DOH//"+domain {
|
||||||
@@ -243,7 +243,7 @@ func (s *DoHNameServer) sendQuery(ctx context.Context, domain string, option dns
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(s.clientIP))
|
reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(clientIP))
|
||||||
|
|
||||||
var deadline time.Time
|
var deadline time.Time
|
||||||
if d, ok := ctx.Deadline(); ok {
|
if d, ok := ctx.Deadline(); ok {
|
||||||
@@ -256,7 +256,7 @@ func (s *DoHNameServer) sendQuery(ctx context.Context, domain string, option dns
|
|||||||
go func(r *dnsRequest) {
|
go func(r *dnsRequest) {
|
||||||
// generate new context for each req, using same context
|
// generate new context for each req, using same context
|
||||||
// may cause reqs all aborted if any one encounter an error
|
// may cause reqs all aborted if any one encounter an error
|
||||||
dnsCtx := context.Background()
|
dnsCtx := ctx
|
||||||
|
|
||||||
// reserve internal dns server requested Inbound
|
// reserve internal dns server requested Inbound
|
||||||
if inbound := session.InboundFromContext(ctx); inbound != nil {
|
if inbound := session.InboundFromContext(ctx); inbound != nil {
|
||||||
@@ -264,8 +264,8 @@ func (s *DoHNameServer) sendQuery(ctx context.Context, domain string, option dns
|
|||||||
}
|
}
|
||||||
|
|
||||||
dnsCtx = session.ContextWithContent(dnsCtx, &session.Content{
|
dnsCtx = session.ContextWithContent(dnsCtx, &session.Content{
|
||||||
Protocol: "https",
|
Protocol: "https",
|
||||||
//SkipRoutePick: true,
|
SkipDNSResolve: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
// forced to use mux for DOH
|
// forced to use mux for DOH
|
||||||
@@ -314,11 +314,11 @@ func (s *DoHNameServer) dohHTTPSContext(ctx context.Context, b []byte) ([]byte,
|
|||||||
|
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
io.Copy(ioutil.Discard, resp.Body) // flush resp.Body so that the conn is reusable
|
io.Copy(io.Discard, resp.Body) // flush resp.Body so that the conn is reusable
|
||||||
return nil, fmt.Errorf("DOH server returned code %d", resp.StatusCode)
|
return nil, fmt.Errorf("DOH server returned code %d", resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ioutil.ReadAll(resp.Body)
|
return io.ReadAll(resp.Body)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DoHNameServer) findIPsForDomain(domain string, option dns_feature.IPOption) ([]net.IP, error) {
|
func (s *DoHNameServer) findIPsForDomain(domain string, option dns_feature.IPOption) ([]net.IP, error) {
|
||||||
@@ -330,30 +330,30 @@ func (s *DoHNameServer) findIPsForDomain(domain string, option dns_feature.IPOpt
|
|||||||
return nil, errRecordNotFound
|
return nil, errRecordNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var err4 error
|
||||||
|
var err6 error
|
||||||
var ips []net.Address
|
var ips []net.Address
|
||||||
var lastErr error
|
var ip6 []net.Address
|
||||||
if option.IPv6Enable && record.AAAA != nil && record.AAAA.RCode == dnsmessage.RCodeSuccess {
|
|
||||||
aaaa, err := record.AAAA.getIPs()
|
if option.IPv4Enable {
|
||||||
if err != nil {
|
ips, err4 = record.A.getIPs()
|
||||||
lastErr = err
|
|
||||||
}
|
|
||||||
ips = append(ips, aaaa...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if option.IPv4Enable && record.A != nil && record.A.RCode == dnsmessage.RCodeSuccess {
|
if option.IPv6Enable {
|
||||||
a, err := record.A.getIPs()
|
ip6, err6 = record.AAAA.getIPs()
|
||||||
if err != nil {
|
ips = append(ips, ip6...)
|
||||||
lastErr = err
|
|
||||||
}
|
|
||||||
ips = append(ips, a...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(ips) > 0 {
|
if len(ips) > 0 {
|
||||||
return toNetIP(ips), nil
|
return toNetIP(ips)
|
||||||
}
|
}
|
||||||
|
|
||||||
if lastErr != nil {
|
if err4 != nil {
|
||||||
return nil, lastErr
|
return nil, err4
|
||||||
|
}
|
||||||
|
|
||||||
|
if err6 != nil {
|
||||||
|
return nil, err6
|
||||||
}
|
}
|
||||||
|
|
||||||
if (option.IPv4Enable && record.A != nil) || (option.IPv6Enable && record.AAAA != nil) {
|
if (option.IPv4Enable && record.A != nil) || (option.IPv6Enable && record.AAAA != nil) {
|
||||||
@@ -363,15 +363,19 @@ func (s *DoHNameServer) findIPsForDomain(domain string, option dns_feature.IPOpt
|
|||||||
return nil, errRecordNotFound
|
return nil, errRecordNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
// QueryIP is called from dns.Server->queryIPTimeout
|
// QueryIP implements Server.
|
||||||
func (s *DoHNameServer) QueryIP(ctx context.Context, domain string, option dns_feature.IPOption) ([]net.IP, error) { // nolint: dupl
|
func (s *DoHNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption, disableCache bool) ([]net.IP, error) { // nolint: dupl
|
||||||
fqdn := Fqdn(domain)
|
fqdn := Fqdn(domain)
|
||||||
|
|
||||||
ips, err := s.findIPsForDomain(fqdn, option)
|
if disableCache {
|
||||||
if err != errRecordNotFound {
|
newError("DNS cache is disabled. Querying IP for ", domain, " at ", s.name).AtDebug().WriteToLog()
|
||||||
newError(s.name, " cache HIT ", domain, " -> ", ips).Base(err).AtDebug().WriteToLog()
|
} else {
|
||||||
log.Record(&log.DNSLog{s.name, domain, ips, log.DNSCacheHit, 0, err})
|
ips, err := s.findIPsForDomain(fqdn, option)
|
||||||
return ips, err
|
if err != errRecordNotFound {
|
||||||
|
newError(s.name, " cache HIT ", domain, " -> ", ips).Base(err).AtDebug().WriteToLog()
|
||||||
|
log.Record(&log.DNSLog{Server: s.name, Domain: domain, Result: ips, Status: log.DNSCacheHit, Elapsed: 0, Error: err})
|
||||||
|
return ips, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ipv4 and ipv6 belong to different subscription groups
|
// ipv4 and ipv6 belong to different subscription groups
|
||||||
@@ -400,13 +404,13 @@ func (s *DoHNameServer) QueryIP(ctx context.Context, domain string, option dns_f
|
|||||||
}
|
}
|
||||||
close(done)
|
close(done)
|
||||||
}()
|
}()
|
||||||
s.sendQuery(ctx, fqdn, option)
|
s.sendQuery(ctx, fqdn, clientIP, option)
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
ips, err := s.findIPsForDomain(fqdn, option)
|
ips, err := s.findIPsForDomain(fqdn, option)
|
||||||
if err != errRecordNotFound {
|
if err != errRecordNotFound {
|
||||||
log.Record(&log.DNSLog{s.name, domain, ips, log.DNSQueried, time.Since(start), err})
|
log.Record(&log.DNSLog{Server: s.name, Domain: domain, Result: ips, Status: log.DNSQueried, Elapsed: time.Since(start), Error: err})
|
||||||
return ips, err
|
return ips, err
|
||||||
}
|
}
|
||||||
|
|
60
app/dns/nameserver_doh_test.go
Normal file
60
app/dns/nameserver_doh_test.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package dns_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
|
||||||
|
. "github.com/xtls/xray-core/app/dns"
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
dns_feature "github.com/xtls/xray-core/features/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDOHNameServer(t *testing.T) {
|
||||||
|
url, err := url.Parse("https+local://1.1.1.1/dns-query")
|
||||||
|
common.Must(err)
|
||||||
|
|
||||||
|
s := NewDoHLocalNameServer(url)
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
|
ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
|
||||||
|
IPv4Enable: true,
|
||||||
|
IPv6Enable: true,
|
||||||
|
}, false)
|
||||||
|
cancel()
|
||||||
|
common.Must(err)
|
||||||
|
if len(ips) == 0 {
|
||||||
|
t.Error("expect some ips, but got 0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDOHNameServerWithCache(t *testing.T) {
|
||||||
|
url, err := url.Parse("https+local://1.1.1.1/dns-query")
|
||||||
|
common.Must(err)
|
||||||
|
|
||||||
|
s := NewDoHLocalNameServer(url)
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
|
ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
|
||||||
|
IPv4Enable: true,
|
||||||
|
IPv6Enable: true,
|
||||||
|
}, false)
|
||||||
|
cancel()
|
||||||
|
common.Must(err)
|
||||||
|
if len(ips) == 0 {
|
||||||
|
t.Error("expect some ips, but got 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx2, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
|
ips2, err := s.QueryIP(ctx2, "google.com", net.IP(nil), dns_feature.IPOption{
|
||||||
|
IPv4Enable: true,
|
||||||
|
IPv6Enable: true,
|
||||||
|
}, true)
|
||||||
|
cancel()
|
||||||
|
common.Must(err)
|
||||||
|
if r := cmp.Diff(ips2, ips); r != "" {
|
||||||
|
t.Fatal(r)
|
||||||
|
}
|
||||||
|
}
|
@@ -20,7 +20,7 @@ func (FakeDNSServer) Name() string {
|
|||||||
return "FakeDNS"
|
return "FakeDNS"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FakeDNSServer) QueryIP(ctx context.Context, domain string, _ dns.IPOption) ([]net.IP, error) {
|
func (f *FakeDNSServer) QueryIP(ctx context.Context, domain string, _ net.IP, opt dns.IPOption, _ bool) ([]net.IP, error) {
|
||||||
if f.fakeDNSEngine == nil {
|
if f.fakeDNSEngine == nil {
|
||||||
if err := core.RequireFeatures(ctx, func(fd dns.FakeDNSEngine) {
|
if err := core.RequireFeatures(ctx, func(fd dns.FakeDNSEngine) {
|
||||||
f.fakeDNSEngine = fd
|
f.fakeDNSEngine = fd
|
||||||
@@ -28,14 +28,22 @@ func (f *FakeDNSServer) QueryIP(ctx context.Context, domain string, _ dns.IPOpti
|
|||||||
return nil, newError("Unable to locate a fake DNS Engine").Base(err).AtError()
|
return nil, newError("Unable to locate a fake DNS Engine").Base(err).AtError()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ips := f.fakeDNSEngine.GetFakeIPForDomain(domain)
|
var ips []net.Address
|
||||||
|
if fkr0, ok := f.fakeDNSEngine.(dns.FakeDNSEngineRev0); ok {
|
||||||
|
ips = fkr0.GetFakeIPForDomain3(domain, opt.IPv4Enable, opt.IPv6Enable)
|
||||||
|
} else {
|
||||||
|
ips = f.fakeDNSEngine.GetFakeIPForDomain(domain)
|
||||||
|
}
|
||||||
|
|
||||||
netIP := toNetIP(ips)
|
netIP, err := toNetIP(ips)
|
||||||
if netIP == nil {
|
if err != nil {
|
||||||
return nil, newError("Unable to convert IP to net ip").AtError()
|
return nil, newError("Unable to convert IP to net ip").Base(err).AtError()
|
||||||
}
|
}
|
||||||
|
|
||||||
newError(f.Name(), " got answer: ", domain, " -> ", ips).AtInfo().WriteToLog()
|
newError(f.Name(), " got answer: ", domain, " -> ", ips).AtInfo().WriteToLog()
|
||||||
|
|
||||||
return netIP, nil
|
if len(netIP) > 0 {
|
||||||
|
return netIP, nil
|
||||||
|
}
|
||||||
|
return nil, dns.ErrEmptyResponse
|
||||||
}
|
}
|
||||||
|
51
app/dns/nameserver_local.go
Normal file
51
app/dns/nameserver_local.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/features/dns"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/features/dns/localdns"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LocalNameServer is an wrapper over local DNS feature.
|
||||||
|
type LocalNameServer struct {
|
||||||
|
client *localdns.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
const errEmptyResponse = "No address associated with hostname"
|
||||||
|
|
||||||
|
// QueryIP implements Server.
|
||||||
|
func (s *LocalNameServer) QueryIP(_ context.Context, domain string, _ net.IP, option dns.IPOption, _ bool) (ips []net.IP, err error) {
|
||||||
|
ips, err = s.client.LookupIP(domain, option)
|
||||||
|
|
||||||
|
if err != nil && strings.HasSuffix(err.Error(), errEmptyResponse) {
|
||||||
|
err = dns.ErrEmptyResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ips) > 0 {
|
||||||
|
newError("Localhost got answer: ", domain, " -> ", ips).AtInfo().WriteToLog()
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name implements Server.
|
||||||
|
func (s *LocalNameServer) Name() string {
|
||||||
|
return "localhost"
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLocalNameServer creates localdns server object for directly lookup in system DNS.
|
||||||
|
func NewLocalNameServer() *LocalNameServer {
|
||||||
|
newError("DNS: created localhost client").AtInfo().WriteToLog()
|
||||||
|
return &LocalNameServer{
|
||||||
|
client: localdns.New(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLocalDNSClient creates localdns client object for directly lookup in system DNS.
|
||||||
|
func NewLocalDNSClient() *Client {
|
||||||
|
return &Client{server: NewLocalNameServer()}
|
||||||
|
}
|
@@ -5,19 +5,21 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
|
||||||
. "github.com/xtls/xray-core/app/dns"
|
. "github.com/xtls/xray-core/app/dns"
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
dns_feature "github.com/xtls/xray-core/features/dns"
|
"github.com/xtls/xray-core/features/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLocalNameServer(t *testing.T) {
|
func TestLocalNameServer(t *testing.T) {
|
||||||
s := NewLocalNameServer()
|
s := NewLocalNameServer()
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
|
||||||
ips, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{
|
ips, err := s.QueryIP(ctx, "google.com", net.IP{}, dns.IPOption{
|
||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
FakeEnable: false,
|
FakeEnable: false,
|
||||||
})
|
}, false)
|
||||||
cancel()
|
cancel()
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
if len(ips) == 0 {
|
if len(ips) == 0 {
|
389
app/dns/nameserver_quic.go
Normal file
389
app/dns/nameserver_quic.go
Normal file
@@ -0,0 +1,389 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/url"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lucas-clemente/quic-go"
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/buf"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/common/protocol/dns"
|
||||||
|
"github.com/xtls/xray-core/common/session"
|
||||||
|
"github.com/xtls/xray-core/common/signal/pubsub"
|
||||||
|
"github.com/xtls/xray-core/common/task"
|
||||||
|
dns_feature "github.com/xtls/xray-core/features/dns"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/tls"
|
||||||
|
"golang.org/x/net/dns/dnsmessage"
|
||||||
|
"golang.org/x/net/http2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NextProtoDQ - During connection establishment, DNS/QUIC support is indicated
|
||||||
|
// by selecting the ALPN token "dq" in the crypto handshake.
|
||||||
|
const NextProtoDQ = "doq-i00"
|
||||||
|
|
||||||
|
const handshakeTimeout = time.Second * 8
|
||||||
|
|
||||||
|
// QUICNameServer implemented DNS over QUIC
|
||||||
|
type QUICNameServer struct {
|
||||||
|
sync.RWMutex
|
||||||
|
ips map[string]*record
|
||||||
|
pub *pubsub.Service
|
||||||
|
cleanup *task.Periodic
|
||||||
|
reqID uint32
|
||||||
|
name string
|
||||||
|
destination *net.Destination
|
||||||
|
session quic.Session
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewQUICNameServer creates DNS-over-QUIC client object for local resolving
|
||||||
|
func NewQUICNameServer(url *url.URL) (*QUICNameServer, error) {
|
||||||
|
newError("DNS: created Local DNS-over-QUIC client for ", url.String()).AtInfo().WriteToLog()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
port := net.Port(784)
|
||||||
|
if url.Port() != "" {
|
||||||
|
port, err = net.PortFromString(url.Port())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dest := net.UDPDestination(net.ParseAddress(url.Hostname()), port)
|
||||||
|
|
||||||
|
s := &QUICNameServer{
|
||||||
|
ips: make(map[string]*record),
|
||||||
|
pub: pubsub.NewService(),
|
||||||
|
name: url.String(),
|
||||||
|
destination: &dest,
|
||||||
|
}
|
||||||
|
s.cleanup = &task.Periodic{
|
||||||
|
Interval: time.Minute,
|
||||||
|
Execute: s.Cleanup,
|
||||||
|
}
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns client name
|
||||||
|
func (s *QUICNameServer) Name() string {
|
||||||
|
return s.name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup clears expired items from cache
|
||||||
|
func (s *QUICNameServer) 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 *QUICNameServer) updateIP(req *dnsRequest, ipRec *IPRecord) {
|
||||||
|
elapsed := time.Since(req.start)
|
||||||
|
|
||||||
|
s.Lock()
|
||||||
|
rec, found := s.ips[req.domain]
|
||||||
|
if !found {
|
||||||
|
rec = &record{}
|
||||||
|
}
|
||||||
|
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 *QUICNameServer) newReqID() uint16 {
|
||||||
|
return uint16(atomic.AddUint32(&s.reqID, 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *QUICNameServer) sendQuery(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption) {
|
||||||
|
newError(s.name, " querying: ", domain).AtInfo().WriteToLog(session.ExportIDToError(ctx))
|
||||||
|
|
||||||
|
reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(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 := ctx
|
||||||
|
|
||||||
|
// 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: "quic",
|
||||||
|
SkipDNSResolve: 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
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := s.openStream(dnsCtx)
|
||||||
|
if err != nil {
|
||||||
|
newError("failed to open quic session").Base(err).AtError().WriteToLog()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = conn.Write(b.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
newError("failed to send query").Base(err).AtError().WriteToLog()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = conn.Close()
|
||||||
|
|
||||||
|
respBuf := buf.New()
|
||||||
|
defer respBuf.Release()
|
||||||
|
n, err := respBuf.ReadFrom(conn)
|
||||||
|
if err != nil && n == 0 {
|
||||||
|
newError("failed to read response").Base(err).AtError().WriteToLog()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rec, err := parseResponse(respBuf.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
newError("failed to handle response").Base(err).AtError().WriteToLog()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.updateIP(r, rec)
|
||||||
|
}(req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *QUICNameServer) findIPsForDomain(domain string, option dns_feature.IPOption) ([]net.IP, error) {
|
||||||
|
s.RLock()
|
||||||
|
record, found := s.ips[domain]
|
||||||
|
s.RUnlock()
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
return nil, errRecordNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
var err4 error
|
||||||
|
var err6 error
|
||||||
|
var ips []net.Address
|
||||||
|
var ip6 []net.Address
|
||||||
|
|
||||||
|
if option.IPv4Enable {
|
||||||
|
ips, err4 = record.A.getIPs()
|
||||||
|
}
|
||||||
|
|
||||||
|
if option.IPv6Enable {
|
||||||
|
ip6, err6 = record.AAAA.getIPs()
|
||||||
|
ips = append(ips, ip6...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ips) > 0 {
|
||||||
|
return toNetIP(ips)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err4 != nil {
|
||||||
|
return nil, err4
|
||||||
|
}
|
||||||
|
|
||||||
|
if err6 != nil {
|
||||||
|
return nil, err6
|
||||||
|
}
|
||||||
|
|
||||||
|
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 *QUICNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption, disableCache bool) ([]net.IP, error) {
|
||||||
|
fqdn := Fqdn(domain)
|
||||||
|
|
||||||
|
if disableCache {
|
||||||
|
newError("DNS cache is disabled. Querying IP for ", domain, " at ", s.name).AtDebug().WriteToLog()
|
||||||
|
} else {
|
||||||
|
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, clientIP, option)
|
||||||
|
|
||||||
|
for {
|
||||||
|
ips, err := s.findIPsForDomain(fqdn, option)
|
||||||
|
if err != errRecordNotFound {
|
||||||
|
return ips, err
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
case <-done:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isActive(s quic.Session) bool {
|
||||||
|
select {
|
||||||
|
case <-s.Context().Done():
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *QUICNameServer) getSession() (quic.Session, error) {
|
||||||
|
var session quic.Session
|
||||||
|
s.RLock()
|
||||||
|
session = s.session
|
||||||
|
if session != nil && isActive(session) {
|
||||||
|
s.RUnlock()
|
||||||
|
return session, nil
|
||||||
|
}
|
||||||
|
if session != nil {
|
||||||
|
// we're recreating the session, let's create a new one
|
||||||
|
_ = session.CloseWithError(0, "")
|
||||||
|
}
|
||||||
|
s.RUnlock()
|
||||||
|
|
||||||
|
s.Lock()
|
||||||
|
defer s.Unlock()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
session, err = s.openSession()
|
||||||
|
if err != nil {
|
||||||
|
// This does not look too nice, but QUIC (or maybe quic-go)
|
||||||
|
// doesn't seem stable enough.
|
||||||
|
// Maybe retransmissions aren't fully implemented in quic-go?
|
||||||
|
// Anyways, the simple solution is to make a second try when
|
||||||
|
// it fails to open the QUIC session.
|
||||||
|
session, err = s.openSession()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.session = session
|
||||||
|
return session, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *QUICNameServer) openSession() (quic.Session, error) {
|
||||||
|
tlsConfig := tls.Config{}
|
||||||
|
quicConfig := &quic.Config{
|
||||||
|
HandshakeIdleTimeout: handshakeTimeout,
|
||||||
|
}
|
||||||
|
|
||||||
|
session, err := quic.DialAddrContext(context.Background(), s.destination.NetAddr(), tlsConfig.GetTLSConfig(tls.WithNextProto("http/1.1", http2.NextProtoTLS, NextProtoDQ)), quicConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return session, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *QUICNameServer) openStream(ctx context.Context) (quic.Stream, error) {
|
||||||
|
session, err := s.getSession()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// open a new stream
|
||||||
|
return session.OpenStreamSync(ctx)
|
||||||
|
}
|
44
app/dns/nameserver_quic_test.go
Normal file
44
app/dns/nameserver_quic_test.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package dns_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/features/dns"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
|
||||||
|
. "github.com/xtls/xray-core/app/dns"
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestQUICNameServer(t *testing.T) {
|
||||||
|
url, err := url.Parse("quic://dns.adguard.com")
|
||||||
|
common.Must(err)
|
||||||
|
s, err := NewQUICNameServer(url)
|
||||||
|
common.Must(err)
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
|
||||||
|
ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns.IPOption{
|
||||||
|
IPv4Enable: true,
|
||||||
|
IPv6Enable: true,
|
||||||
|
}, false)
|
||||||
|
cancel()
|
||||||
|
common.Must(err)
|
||||||
|
if len(ips) == 0 {
|
||||||
|
t.Error("expect some ips, but got 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx2, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
|
ips2, err := s.QueryIP(ctx2, "google.com", net.IP(nil), dns.IPOption{
|
||||||
|
IPv4Enable: true,
|
||||||
|
IPv6Enable: true,
|
||||||
|
}, true)
|
||||||
|
cancel()
|
||||||
|
common.Must(err)
|
||||||
|
if r := cmp.Diff(ips2, ips); r != "" {
|
||||||
|
t.Fatal(r)
|
||||||
|
}
|
||||||
|
}
|
362
app/dns/nameserver_tcp.go
Normal file
362
app/dns/nameserver_tcp.go
Normal file
@@ -0,0 +1,362 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
|
"net/url"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/dns/dnsmessage"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/buf"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/common/net/cnc"
|
||||||
|
"github.com/xtls/xray-core/common/protocol/dns"
|
||||||
|
"github.com/xtls/xray-core/common/session"
|
||||||
|
"github.com/xtls/xray-core/common/signal/pubsub"
|
||||||
|
"github.com/xtls/xray-core/common/task"
|
||||||
|
dns_feature "github.com/xtls/xray-core/features/dns"
|
||||||
|
"github.com/xtls/xray-core/features/routing"
|
||||||
|
"github.com/xtls/xray-core/transport/internet"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TCPNameServer implemented DNS over TCP (RFC7766).
|
||||||
|
type TCPNameServer struct {
|
||||||
|
sync.RWMutex
|
||||||
|
name string
|
||||||
|
destination *net.Destination
|
||||||
|
ips map[string]*record
|
||||||
|
pub *pubsub.Service
|
||||||
|
cleanup *task.Periodic
|
||||||
|
reqID uint32
|
||||||
|
dial func(context.Context) (net.Conn, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTCPNameServer creates DNS over TCP server object for remote resolving.
|
||||||
|
func NewTCPNameServer(url *url.URL, dispatcher routing.Dispatcher) (*TCPNameServer, error) {
|
||||||
|
s, err := baseTCPNameServer(url, "TCP")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.dial = func(ctx context.Context) (net.Conn, error) {
|
||||||
|
link, err := dispatcher.Dispatch(ctx, *s.destination)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cnc.NewConnection(
|
||||||
|
cnc.ConnectionInputMulti(link.Writer),
|
||||||
|
cnc.ConnectionOutputMulti(link.Reader),
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTCPLocalNameServer creates DNS over TCP client object for local resolving
|
||||||
|
func NewTCPLocalNameServer(url *url.URL) (*TCPNameServer, error) {
|
||||||
|
s, err := baseTCPNameServer(url, "TCPL")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.dial = func(ctx context.Context) (net.Conn, error) {
|
||||||
|
return internet.DialSystem(ctx, *s.destination, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func baseTCPNameServer(url *url.URL, prefix string) (*TCPNameServer, error) {
|
||||||
|
var err error
|
||||||
|
port := net.Port(53)
|
||||||
|
if url.Port() != "" {
|
||||||
|
port, err = net.PortFromString(url.Port())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dest := net.TCPDestination(net.ParseAddress(url.Hostname()), port)
|
||||||
|
|
||||||
|
s := &TCPNameServer{
|
||||||
|
destination: &dest,
|
||||||
|
ips: make(map[string]*record),
|
||||||
|
pub: pubsub.NewService(),
|
||||||
|
name: prefix + "//" + dest.NetAddr(),
|
||||||
|
}
|
||||||
|
s.cleanup = &task.Periodic{
|
||||||
|
Interval: time.Minute,
|
||||||
|
Execute: s.Cleanup,
|
||||||
|
}
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name implements Server.
|
||||||
|
func (s *TCPNameServer) Name() string {
|
||||||
|
return s.name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup clears expired items from cache
|
||||||
|
func (s *TCPNameServer) 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 *TCPNameServer) updateIP(req *dnsRequest, ipRec *IPRecord) {
|
||||||
|
elapsed := time.Since(req.start)
|
||||||
|
|
||||||
|
s.Lock()
|
||||||
|
rec, found := s.ips[req.domain]
|
||||||
|
if !found {
|
||||||
|
rec = &record{}
|
||||||
|
}
|
||||||
|
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 *TCPNameServer) newReqID() uint16 {
|
||||||
|
return uint16(atomic.AddUint32(&s.reqID, 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TCPNameServer) sendQuery(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption) {
|
||||||
|
newError(s.name, " querying DNS for: ", domain).AtDebug().WriteToLog(session.ExportIDToError(ctx))
|
||||||
|
|
||||||
|
reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(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) {
|
||||||
|
dnsCtx := ctx
|
||||||
|
|
||||||
|
if inbound := session.InboundFromContext(ctx); inbound != nil {
|
||||||
|
dnsCtx = session.ContextWithInbound(dnsCtx, inbound)
|
||||||
|
}
|
||||||
|
|
||||||
|
dnsCtx = session.ContextWithContent(dnsCtx, &session.Content{
|
||||||
|
Protocol: "dns",
|
||||||
|
SkipDNSResolve: 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
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := s.dial(dnsCtx)
|
||||||
|
if err != nil {
|
||||||
|
newError("failed to dial namesever").Base(err).AtError().WriteToLog()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
dnsReqBuf := buf.New()
|
||||||
|
binary.Write(dnsReqBuf, binary.BigEndian, uint16(b.Len()))
|
||||||
|
dnsReqBuf.Write(b.Bytes())
|
||||||
|
b.Release()
|
||||||
|
|
||||||
|
_, err = conn.Write(dnsReqBuf.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
newError("failed to send query").Base(err).AtError().WriteToLog()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dnsReqBuf.Release()
|
||||||
|
|
||||||
|
respBuf := buf.New()
|
||||||
|
defer respBuf.Release()
|
||||||
|
n, err := respBuf.ReadFullFrom(conn, 2)
|
||||||
|
if err != nil && n == 0 {
|
||||||
|
newError("failed to read response length").Base(err).AtError().WriteToLog()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var length int16
|
||||||
|
err = binary.Read(bytes.NewReader(respBuf.Bytes()), binary.BigEndian, &length)
|
||||||
|
if err != nil {
|
||||||
|
newError("failed to parse response length").Base(err).AtError().WriteToLog()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
respBuf.Clear()
|
||||||
|
n, err = respBuf.ReadFullFrom(conn, int32(length))
|
||||||
|
if err != nil && n == 0 {
|
||||||
|
newError("failed to read response length").Base(err).AtError().WriteToLog()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rec, err := parseResponse(respBuf.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
newError("failed to parse DNS over TCP response").Base(err).AtError().WriteToLog()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s.updateIP(r, rec)
|
||||||
|
}(req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TCPNameServer) findIPsForDomain(domain string, option dns_feature.IPOption) ([]net.IP, error) {
|
||||||
|
s.RLock()
|
||||||
|
record, found := s.ips[domain]
|
||||||
|
s.RUnlock()
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
return nil, errRecordNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
var err4 error
|
||||||
|
var err6 error
|
||||||
|
var ips []net.Address
|
||||||
|
var ip6 []net.Address
|
||||||
|
|
||||||
|
if option.IPv4Enable {
|
||||||
|
ips, err4 = record.A.getIPs()
|
||||||
|
}
|
||||||
|
|
||||||
|
if option.IPv6Enable {
|
||||||
|
ip6, err6 = record.AAAA.getIPs()
|
||||||
|
ips = append(ips, ip6...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ips) > 0 {
|
||||||
|
return toNetIP(ips)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err4 != nil {
|
||||||
|
return nil, err4
|
||||||
|
}
|
||||||
|
|
||||||
|
if err6 != nil {
|
||||||
|
return nil, err6
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, dns_feature.ErrEmptyResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryIP implements Server.
|
||||||
|
func (s *TCPNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption, disableCache bool) ([]net.IP, error) {
|
||||||
|
fqdn := Fqdn(domain)
|
||||||
|
|
||||||
|
if disableCache {
|
||||||
|
newError("DNS cache is disabled. Querying IP for ", domain, " at ", s.name).AtDebug().WriteToLog()
|
||||||
|
} else {
|
||||||
|
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, clientIP, option)
|
||||||
|
|
||||||
|
for {
|
||||||
|
ips, err := s.findIPsForDomain(fqdn, option)
|
||||||
|
if err != errRecordNotFound {
|
||||||
|
return ips, err
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
case <-done:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
60
app/dns/nameserver_tcp_test.go
Normal file
60
app/dns/nameserver_tcp_test.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package dns_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
|
||||||
|
. "github.com/xtls/xray-core/app/dns"
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
dns_feature "github.com/xtls/xray-core/features/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTCPLocalNameServer(t *testing.T) {
|
||||||
|
url, err := url.Parse("tcp+local://8.8.8.8")
|
||||||
|
common.Must(err)
|
||||||
|
s, err := NewTCPLocalNameServer(url)
|
||||||
|
common.Must(err)
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
|
ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
|
||||||
|
IPv4Enable: true,
|
||||||
|
IPv6Enable: true,
|
||||||
|
}, false)
|
||||||
|
cancel()
|
||||||
|
common.Must(err)
|
||||||
|
if len(ips) == 0 {
|
||||||
|
t.Error("expect some ips, but got 0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTCPLocalNameServerWithCache(t *testing.T) {
|
||||||
|
url, err := url.Parse("tcp+local://8.8.8.8")
|
||||||
|
common.Must(err)
|
||||||
|
s, err := NewTCPLocalNameServer(url)
|
||||||
|
common.Must(err)
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
|
ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
|
||||||
|
IPv4Enable: true,
|
||||||
|
IPv6Enable: true,
|
||||||
|
}, false)
|
||||||
|
cancel()
|
||||||
|
common.Must(err)
|
||||||
|
if len(ips) == 0 {
|
||||||
|
t.Error("expect some ips, but got 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx2, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
|
ips2, err := s.QueryIP(ctx2, "google.com", net.IP(nil), dns_feature.IPOption{
|
||||||
|
IPv4Enable: true,
|
||||||
|
IPv6Enable: true,
|
||||||
|
}, true)
|
||||||
|
cancel()
|
||||||
|
common.Must(err)
|
||||||
|
if r := cmp.Diff(ips2, ips); r != "" {
|
||||||
|
t.Fatal(r)
|
||||||
|
}
|
||||||
|
}
|
@@ -7,6 +7,8 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/dns/dnsmessage"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/log"
|
"github.com/xtls/xray-core/common/log"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
@@ -18,33 +20,32 @@ import (
|
|||||||
dns_feature "github.com/xtls/xray-core/features/dns"
|
dns_feature "github.com/xtls/xray-core/features/dns"
|
||||||
"github.com/xtls/xray-core/features/routing"
|
"github.com/xtls/xray-core/features/routing"
|
||||||
"github.com/xtls/xray-core/transport/internet/udp"
|
"github.com/xtls/xray-core/transport/internet/udp"
|
||||||
"golang.org/x/net/dns/dnsmessage"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ClassicNameServer implemented traditional UDP DNS.
|
||||||
type ClassicNameServer struct {
|
type ClassicNameServer struct {
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
name string
|
name string
|
||||||
address net.Destination
|
address *net.Destination
|
||||||
ips map[string]record
|
ips map[string]*record
|
||||||
requests map[uint16]dnsRequest
|
requests map[uint16]*dnsRequest
|
||||||
pub *pubsub.Service
|
pub *pubsub.Service
|
||||||
udpServer *udp.Dispatcher
|
udpServer *udp.Dispatcher
|
||||||
cleanup *task.Periodic
|
cleanup *task.Periodic
|
||||||
reqID uint32
|
reqID uint32
|
||||||
clientIP net.IP
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClassicNameServer(address net.Destination, dispatcher routing.Dispatcher, clientIP net.IP) *ClassicNameServer {
|
// NewClassicNameServer creates udp server object for remote resolving.
|
||||||
|
func NewClassicNameServer(address net.Destination, dispatcher routing.Dispatcher) *ClassicNameServer {
|
||||||
// default to 53 if unspecific
|
// default to 53 if unspecific
|
||||||
if address.Port == 0 {
|
if address.Port == 0 {
|
||||||
address.Port = net.Port(53)
|
address.Port = net.Port(53)
|
||||||
}
|
}
|
||||||
|
|
||||||
s := &ClassicNameServer{
|
s := &ClassicNameServer{
|
||||||
address: address,
|
address: &address,
|
||||||
ips: make(map[string]record),
|
ips: make(map[string]*record),
|
||||||
requests: make(map[uint16]dnsRequest),
|
requests: make(map[uint16]*dnsRequest),
|
||||||
clientIP: clientIP,
|
|
||||||
pub: pubsub.NewService(),
|
pub: pubsub.NewService(),
|
||||||
name: strings.ToUpper(address.String()),
|
name: strings.ToUpper(address.String()),
|
||||||
}
|
}
|
||||||
@@ -57,10 +58,12 @@ func NewClassicNameServer(address net.Destination, dispatcher routing.Dispatcher
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Name implements Server.
|
||||||
func (s *ClassicNameServer) Name() string {
|
func (s *ClassicNameServer) Name() string {
|
||||||
return s.name
|
return s.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cleanup clears expired items from cache
|
||||||
func (s *ClassicNameServer) Cleanup() error {
|
func (s *ClassicNameServer) Cleanup() error {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
s.Lock()
|
s.Lock()
|
||||||
@@ -79,6 +82,7 @@ func (s *ClassicNameServer) Cleanup() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if record.A == nil && record.AAAA == nil {
|
if record.A == nil && record.AAAA == nil {
|
||||||
|
newError(s.name, " cleanup ", domain).AtDebug().WriteToLog()
|
||||||
delete(s.ips, domain)
|
delete(s.ips, domain)
|
||||||
} else {
|
} else {
|
||||||
s.ips[domain] = record
|
s.ips[domain] = record
|
||||||
@@ -86,7 +90,7 @@ func (s *ClassicNameServer) Cleanup() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(s.ips) == 0 {
|
if len(s.ips) == 0 {
|
||||||
s.ips = make(map[string]record)
|
s.ips = make(map[string]*record)
|
||||||
}
|
}
|
||||||
|
|
||||||
for id, req := range s.requests {
|
for id, req := range s.requests {
|
||||||
@@ -96,12 +100,13 @@ func (s *ClassicNameServer) Cleanup() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(s.requests) == 0 {
|
if len(s.requests) == 0 {
|
||||||
s.requests = make(map[uint16]dnsRequest)
|
s.requests = make(map[uint16]*dnsRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HandleResponse handles udp response packet from remote DNS server.
|
||||||
func (s *ClassicNameServer) HandleResponse(ctx context.Context, packet *udp_proto.Packet) {
|
func (s *ClassicNameServer) HandleResponse(ctx context.Context, packet *udp_proto.Packet) {
|
||||||
ipRec, err := parseResponse(packet.Payload.Bytes())
|
ipRec, err := parseResponse(packet.Payload.Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -133,15 +138,17 @@ func (s *ClassicNameServer) HandleResponse(ctx context.Context, packet *udp_prot
|
|||||||
elapsed := time.Since(req.start)
|
elapsed := time.Since(req.start)
|
||||||
newError(s.name, " got answer: ", req.domain, " ", req.reqType, " -> ", ipRec.IP, " ", elapsed).AtInfo().WriteToLog()
|
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) {
|
if len(req.domain) > 0 && (rec.A != nil || rec.AAAA != nil) {
|
||||||
s.updateIP(req.domain, rec)
|
s.updateIP(req.domain, &rec)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ClassicNameServer) updateIP(domain string, newRec record) {
|
func (s *ClassicNameServer) updateIP(domain string, newRec *record) {
|
||||||
s.Lock()
|
s.Lock()
|
||||||
|
|
||||||
newError(s.name, " updating IP records for domain:", domain).AtDebug().WriteToLog()
|
rec, found := s.ips[domain]
|
||||||
rec := s.ips[domain]
|
if !found {
|
||||||
|
rec = &record{}
|
||||||
|
}
|
||||||
|
|
||||||
updated := false
|
updated := false
|
||||||
if isNewer(rec.A, newRec.A) {
|
if isNewer(rec.A, newRec.A) {
|
||||||
@@ -154,6 +161,7 @@ func (s *ClassicNameServer) updateIP(domain string, newRec record) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if updated {
|
if updated {
|
||||||
|
newError(s.name, " updating IP records for domain:", domain).AtDebug().WriteToLog()
|
||||||
s.ips[domain] = rec
|
s.ips[domain] = rec
|
||||||
}
|
}
|
||||||
if newRec.A != nil {
|
if newRec.A != nil {
|
||||||
@@ -176,13 +184,13 @@ func (s *ClassicNameServer) addPendingRequest(req *dnsRequest) {
|
|||||||
|
|
||||||
id := req.msg.ID
|
id := req.msg.ID
|
||||||
req.expire = time.Now().Add(time.Second * 8)
|
req.expire = time.Now().Add(time.Second * 8)
|
||||||
s.requests[id] = *req
|
s.requests[id] = req
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ClassicNameServer) sendQuery(ctx context.Context, domain string, option dns_feature.IPOption) {
|
func (s *ClassicNameServer) sendQuery(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption) {
|
||||||
newError(s.name, " querying DNS for: ", domain).AtDebug().WriteToLog(session.ExportIDToError(ctx))
|
newError(s.name, " querying DNS for: ", domain).AtDebug().WriteToLog(session.ExportIDToError(ctx))
|
||||||
|
|
||||||
reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(s.clientIP))
|
reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(clientIP))
|
||||||
|
|
||||||
for _, req := range reqs {
|
for _, req := range reqs {
|
||||||
s.addPendingRequest(req)
|
s.addPendingRequest(req)
|
||||||
@@ -201,7 +209,7 @@ func (s *ClassicNameServer) sendQuery(ctx context.Context, domain string, option
|
|||||||
Status: log.AccessAccepted,
|
Status: log.AccessAccepted,
|
||||||
Reason: "",
|
Reason: "",
|
||||||
})
|
})
|
||||||
s.udpServer.Dispatch(udpCtx, s.address, b)
|
s.udpServer.Dispatch(udpCtx, *s.address, b)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,44 +222,48 @@ func (s *ClassicNameServer) findIPsForDomain(domain string, option dns_feature.I
|
|||||||
return nil, errRecordNotFound
|
return nil, errRecordNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var err4 error
|
||||||
|
var err6 error
|
||||||
var ips []net.Address
|
var ips []net.Address
|
||||||
var lastErr error
|
var ip6 []net.Address
|
||||||
|
|
||||||
if option.IPv4Enable {
|
if option.IPv4Enable {
|
||||||
a, err := record.A.getIPs()
|
ips, err4 = record.A.getIPs()
|
||||||
if err != nil {
|
|
||||||
lastErr = err
|
|
||||||
}
|
|
||||||
ips = append(ips, a...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if option.IPv6Enable {
|
if option.IPv6Enable {
|
||||||
aaaa, err := record.AAAA.getIPs()
|
ip6, err6 = record.AAAA.getIPs()
|
||||||
if err != nil {
|
ips = append(ips, ip6...)
|
||||||
lastErr = err
|
|
||||||
}
|
|
||||||
ips = append(ips, aaaa...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(ips) > 0 {
|
if len(ips) > 0 {
|
||||||
return toNetIP(ips), nil
|
return toNetIP(ips)
|
||||||
}
|
}
|
||||||
|
|
||||||
if lastErr != nil {
|
if err4 != nil {
|
||||||
return nil, lastErr
|
return nil, err4
|
||||||
|
}
|
||||||
|
|
||||||
|
if err6 != nil {
|
||||||
|
return nil, err6
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, dns_feature.ErrEmptyResponse
|
return nil, dns_feature.ErrEmptyResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
// QueryIP implements Server.
|
// QueryIP implements Server.
|
||||||
func (s *ClassicNameServer) QueryIP(ctx context.Context, domain string, option dns_feature.IPOption) ([]net.IP, error) {
|
func (s *ClassicNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption, disableCache bool) ([]net.IP, error) {
|
||||||
fqdn := Fqdn(domain)
|
fqdn := Fqdn(domain)
|
||||||
|
|
||||||
ips, err := s.findIPsForDomain(fqdn, option)
|
if disableCache {
|
||||||
if err != errRecordNotFound {
|
newError("DNS cache is disabled. Querying IP for ", domain, " at ", s.name).AtDebug().WriteToLog()
|
||||||
newError(s.name, " cache HIT ", domain, " -> ", ips).Base(err).AtDebug().WriteToLog()
|
} else {
|
||||||
log.Record(&log.DNSLog{s.name, domain, ips, log.DNSCacheHit, 0, err})
|
ips, err := s.findIPsForDomain(fqdn, option)
|
||||||
return ips, err
|
if err != errRecordNotFound {
|
||||||
|
newError(s.name, " cache HIT ", domain, " -> ", ips).Base(err).AtDebug().WriteToLog()
|
||||||
|
log.Record(&log.DNSLog{Server: s.name, Domain: domain, Result: ips, Status: log.DNSCacheHit, Elapsed: 0, Error: err})
|
||||||
|
return ips, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ipv4 and ipv6 belong to different subscription groups
|
// ipv4 and ipv6 belong to different subscription groups
|
||||||
@@ -280,13 +292,13 @@ func (s *ClassicNameServer) QueryIP(ctx context.Context, domain string, option d
|
|||||||
}
|
}
|
||||||
close(done)
|
close(done)
|
||||||
}()
|
}()
|
||||||
s.sendQuery(ctx, fqdn, option)
|
s.sendQuery(ctx, fqdn, clientIP, option)
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
ips, err := s.findIPsForDomain(fqdn, option)
|
ips, err := s.findIPsForDomain(fqdn, option)
|
||||||
if err != errRecordNotFound {
|
if err != errRecordNotFound {
|
||||||
log.Record(&log.DNSLog{s.name, domain, ips, log.DNSQueried, time.Since(start), err})
|
log.Record(&log.DNSLog{Server: s.name, Domain: domain, Result: ips, Status: log.DNSQueried, Elapsed: time.Since(start), Error: err})
|
||||||
return ips, err
|
return ips, err
|
||||||
}
|
}
|
||||||
|
|
@@ -1,437 +0,0 @@
|
|||||||
package dns
|
|
||||||
|
|
||||||
//go:generate go run github.com/xtls/xray-core/common/errors/errorgen
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/xtls/xray-core/app/router"
|
|
||||||
"github.com/xtls/xray-core/common"
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
|
||||||
"github.com/xtls/xray-core/common/net"
|
|
||||||
"github.com/xtls/xray-core/common/session"
|
|
||||||
"github.com/xtls/xray-core/common/strmatcher"
|
|
||||||
"github.com/xtls/xray-core/common/uuid"
|
|
||||||
core "github.com/xtls/xray-core/core"
|
|
||||||
"github.com/xtls/xray-core/features"
|
|
||||||
"github.com/xtls/xray-core/features/dns"
|
|
||||||
"github.com/xtls/xray-core/features/routing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Server is a DNS rely server.
|
|
||||||
type Server struct {
|
|
||||||
sync.Mutex
|
|
||||||
hosts *StaticHosts
|
|
||||||
clientIP net.IP
|
|
||||||
clients []Client // clientIdx -> Client
|
|
||||||
ctx context.Context
|
|
||||||
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)),
|
|
||||||
ctx: ctx,
|
|
||||||
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
|
|
||||||
}))
|
|
||||||
|
|
||||||
case address.Family().IsDomain() && address.Domain() == "fakedns":
|
|
||||||
server.clients = append(server.clients, NewFakeDNSServer())
|
|
||||||
|
|
||||||
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 dns.IPOption) ([]net.IP, error) {
|
|
||||||
ctx, cancel := context.WithTimeout(s.ctx, 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
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) lookupStatic(domain string, option dns.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
|
|
||||||
}
|
|
||||||
|
|
||||||
// LookupIP implements dns.Client.
|
|
||||||
func (s *Server) LookupIP(domain string, option dns.IPOption) ([]net.IP, error) {
|
|
||||||
if domain == "" {
|
|
||||||
return nil, newError("empty domain name")
|
|
||||||
}
|
|
||||||
|
|
||||||
// normalize the FQDN form query
|
|
||||||
if strings.HasSuffix(domain, ".") {
|
|
||||||
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]
|
|
||||||
if !option.FakeEnable && strings.EqualFold(matchedClient.Name(), "FakeDNS") {
|
|
||||||
newError("skip DNS resolution for domain ", domain, " at server ", matchedClient.Name()).AtDebug().WriteToLog()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
if !option.FakeEnable && strings.EqualFold(client.Name(), "FakeDNS") {
|
|
||||||
newError("skip DNS resolution for domain ", domain, " at server ", client.Name()).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))
|
|
||||||
}))
|
|
||||||
}
|
|
@@ -1,13 +1,12 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.25.0
|
// protoc-gen-go v1.27.1
|
||||||
// protoc v3.14.0
|
// protoc v3.18.0
|
||||||
// source: app/log/command/config.proto
|
// source: app/log/command/config.proto
|
||||||
|
|
||||||
package command
|
package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
proto "github.com/golang/protobuf/proto"
|
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
@@ -21,10 +20,6 @@ const (
|
|||||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
_ = 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 {
|
type Config struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
|
@@ -1,13 +1,12 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.25.0
|
// protoc-gen-go v1.27.1
|
||||||
// protoc v3.14.0
|
// protoc v3.18.0
|
||||||
// source: app/log/config.proto
|
// source: app/log/config.proto
|
||||||
|
|
||||||
package log
|
package log
|
||||||
|
|
||||||
import (
|
import (
|
||||||
proto "github.com/golang/protobuf/proto"
|
|
||||||
log "github.com/xtls/xray-core/common/log"
|
log "github.com/xtls/xray-core/common/log"
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
@@ -22,10 +21,6 @@ const (
|
|||||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
_ = 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
|
type LogType int32
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -134,7 +129,7 @@ func (x *Config) GetErrorLogLevel() log.Severity {
|
|||||||
if x != nil {
|
if x != nil {
|
||||||
return x.ErrorLogLevel
|
return x.ErrorLogLevel
|
||||||
}
|
}
|
||||||
return log.Severity_Unknown
|
return log.Severity(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Config) GetErrorLogPath() string {
|
func (x *Config) GetErrorLogPath() string {
|
||||||
|
@@ -11,9 +11,7 @@ type HandlerCreatorOptions struct {
|
|||||||
|
|
||||||
type HandlerCreator func(LogType, HandlerCreatorOptions) (log.Handler, error)
|
type HandlerCreator func(LogType, HandlerCreatorOptions) (log.Handler, error)
|
||||||
|
|
||||||
var (
|
var handlerCreatorMap = make(map[LogType]HandlerCreator)
|
||||||
handlerCreatorMap = make(map[LogType]HandlerCreator)
|
|
||||||
)
|
|
||||||
|
|
||||||
func RegisterHandlerCreator(logType LogType, f HandlerCreator) error {
|
func RegisterHandlerCreator(logType LogType, f HandlerCreator) error {
|
||||||
if f == nil {
|
if f == nil {
|
||||||
|
@@ -5,6 +5,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/golang/mock/gomock"
|
"github.com/golang/mock/gomock"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/app/log"
|
"github.com/xtls/xray-core/app/log"
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
clog "github.com/xtls/xray-core/common/log"
|
clog "github.com/xtls/xray-core/common/log"
|
||||||
|
@@ -1,13 +1,12 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.25.0
|
// protoc-gen-go v1.27.1
|
||||||
// protoc v3.14.0
|
// protoc v3.18.0
|
||||||
// source: app/policy/config.proto
|
// source: app/policy/config.proto
|
||||||
|
|
||||||
package policy
|
package policy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
proto "github.com/golang/protobuf/proto"
|
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
@@ -21,10 +20,6 @@ const (
|
|||||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
_ = 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 {
|
type Second struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
|
@@ -1,13 +1,12 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.25.0
|
// protoc-gen-go v1.27.1
|
||||||
// protoc v3.14.0
|
// protoc v3.18.0
|
||||||
// source: app/proxyman/command/command.proto
|
// source: app/proxyman/command/command.proto
|
||||||
|
|
||||||
package command
|
package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
proto "github.com/golang/protobuf/proto"
|
|
||||||
protocol "github.com/xtls/xray-core/common/protocol"
|
protocol "github.com/xtls/xray-core/common/protocol"
|
||||||
serial "github.com/xtls/xray-core/common/serial"
|
serial "github.com/xtls/xray-core/common/serial"
|
||||||
core "github.com/xtls/xray-core/core"
|
core "github.com/xtls/xray-core/core"
|
||||||
@@ -24,10 +23,6 @@ const (
|
|||||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
_ = 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 AddUserOperation struct {
|
type AddUserOperation struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
|
@@ -1,13 +1,12 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.25.0
|
// protoc-gen-go v1.27.1
|
||||||
// protoc (unknown)
|
// protoc v3.18.0
|
||||||
// source: app/proxyman/config.proto
|
// source: app/proxyman/config.proto
|
||||||
|
|
||||||
package proxyman
|
package proxyman
|
||||||
|
|
||||||
import (
|
import (
|
||||||
proto "github.com/golang/protobuf/proto"
|
|
||||||
net "github.com/xtls/xray-core/common/net"
|
net "github.com/xtls/xray-core/common/net"
|
||||||
serial "github.com/xtls/xray-core/common/serial"
|
serial "github.com/xtls/xray-core/common/serial"
|
||||||
internet "github.com/xtls/xray-core/transport/internet"
|
internet "github.com/xtls/xray-core/transport/internet"
|
||||||
@@ -24,10 +23,6 @@ const (
|
|||||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
_ = 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 KnownProtocols int32
|
type KnownProtocols int32
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@@ -6,6 +6,8 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/transport/internet/stat"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/app/proxyman"
|
"github.com/xtls/xray-core/app/proxyman"
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/buf"
|
"github.com/xtls/xray-core/common/buf"
|
||||||
@@ -54,7 +56,7 @@ func getTProxyType(s *internet.MemoryStreamConfig) internet.SocketConfig_TProxyM
|
|||||||
return s.SocketSettings.Tproxy
|
return s.SocketSettings.Tproxy
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *tcpWorker) callback(conn internet.Connection) {
|
func (w *tcpWorker) callback(conn stat.Connection) {
|
||||||
ctx, cancel := context.WithCancel(w.ctx)
|
ctx, cancel := context.WithCancel(w.ctx)
|
||||||
sid := session.NewID()
|
sid := session.NewID()
|
||||||
ctx = session.ContextWithID(ctx, sid)
|
ctx = session.ContextWithID(ctx, sid)
|
||||||
@@ -80,7 +82,7 @@ func (w *tcpWorker) callback(conn internet.Connection) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if w.uplinkCounter != nil || w.downlinkCounter != nil {
|
if w.uplinkCounter != nil || w.downlinkCounter != nil {
|
||||||
conn = &internet.StatCouterConnection{
|
conn = &stat.CounterConnection{
|
||||||
Connection: conn,
|
Connection: conn,
|
||||||
ReadCounter: w.uplinkCounter,
|
ReadCounter: w.uplinkCounter,
|
||||||
WriteCounter: w.downlinkCounter,
|
WriteCounter: w.downlinkCounter,
|
||||||
@@ -117,7 +119,7 @@ func (w *tcpWorker) Proxy() proxy.Inbound {
|
|||||||
|
|
||||||
func (w *tcpWorker) Start() error {
|
func (w *tcpWorker) Start() error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
hub, err := internet.ListenTCP(ctx, w.address, w.port, w.stream, func(conn internet.Connection) {
|
hub, err := internet.ListenTCP(ctx, w.address, w.port, w.stream, func(conn stat.Connection) {
|
||||||
go w.callback(conn)
|
go w.callback(conn)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -436,13 +438,13 @@ type dsWorker struct {
|
|||||||
ctx context.Context
|
ctx context.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *dsWorker) callback(conn internet.Connection) {
|
func (w *dsWorker) callback(conn stat.Connection) {
|
||||||
ctx, cancel := context.WithCancel(w.ctx)
|
ctx, cancel := context.WithCancel(w.ctx)
|
||||||
sid := session.NewID()
|
sid := session.NewID()
|
||||||
ctx = session.ContextWithID(ctx, sid)
|
ctx = session.ContextWithID(ctx, sid)
|
||||||
|
|
||||||
if w.uplinkCounter != nil || w.downlinkCounter != nil {
|
if w.uplinkCounter != nil || w.downlinkCounter != nil {
|
||||||
conn = &internet.StatCouterConnection{
|
conn = &stat.CounterConnection{
|
||||||
Connection: conn,
|
Connection: conn,
|
||||||
ReadCounter: w.uplinkCounter,
|
ReadCounter: w.uplinkCounter,
|
||||||
WriteCounter: w.downlinkCounter,
|
WriteCounter: w.downlinkCounter,
|
||||||
@@ -480,9 +482,10 @@ func (w *dsWorker) Proxy() proxy.Inbound {
|
|||||||
func (w *dsWorker) Port() net.Port {
|
func (w *dsWorker) Port() net.Port {
|
||||||
return net.Port(0)
|
return net.Port(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *dsWorker) Start() error {
|
func (w *dsWorker) Start() error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
hub, err := internet.ListenUnix(ctx, w.address, w.stream, func(conn internet.Connection) {
|
hub, err := internet.ListenUnix(ctx, w.address, w.stream, func(conn stat.Connection) {
|
||||||
go w.callback(conn)
|
go w.callback(conn)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@@ -3,6 +3,8 @@ package outbound
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/transport/internet/stat"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/app/proxyman"
|
"github.com/xtls/xray-core/app/proxyman"
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/mux"
|
"github.com/xtls/xray-core/common/mux"
|
||||||
@@ -158,7 +160,7 @@ func (h *Handler) Address() net.Address {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Dial implements internet.Dialer.
|
// Dial implements internet.Dialer.
|
||||||
func (h *Handler) Dial(ctx context.Context, dest net.Destination) (internet.Connection, error) {
|
func (h *Handler) Dial(ctx context.Context, dest net.Destination) (stat.Connection, error) {
|
||||||
if h.senderSettings != nil {
|
if h.senderSettings != nil {
|
||||||
if h.senderSettings.ProxySettings.HasTag() {
|
if h.senderSettings.ProxySettings.HasTag() {
|
||||||
tag := h.senderSettings.ProxySettings.Tag
|
tag := h.senderSettings.ProxySettings.Tag
|
||||||
@@ -201,9 +203,9 @@ func (h *Handler) Dial(ctx context.Context, dest net.Destination) (internet.Conn
|
|||||||
return h.getStatCouterConnection(conn), err
|
return h.getStatCouterConnection(conn), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) getStatCouterConnection(conn internet.Connection) internet.Connection {
|
func (h *Handler) getStatCouterConnection(conn stat.Connection) stat.Connection {
|
||||||
if h.uplinkCounter != nil || h.downlinkCounter != nil {
|
if h.uplinkCounter != nil || h.downlinkCounter != nil {
|
||||||
return &internet.StatCouterConnection{
|
return &stat.CounterConnection{
|
||||||
Connection: conn,
|
Connection: conn,
|
||||||
ReadCounter: h.downlinkCounter,
|
ReadCounter: h.downlinkCounter,
|
||||||
WriteCounter: h.uplinkCounter,
|
WriteCounter: h.uplinkCounter,
|
||||||
|
@@ -4,6 +4,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/transport/internet/stat"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/app/policy"
|
"github.com/xtls/xray-core/app/policy"
|
||||||
. "github.com/xtls/xray-core/app/proxyman/outbound"
|
. "github.com/xtls/xray-core/app/proxyman/outbound"
|
||||||
"github.com/xtls/xray-core/app/stats"
|
"github.com/xtls/xray-core/app/stats"
|
||||||
@@ -12,7 +14,6 @@ import (
|
|||||||
core "github.com/xtls/xray-core/core"
|
core "github.com/xtls/xray-core/core"
|
||||||
"github.com/xtls/xray-core/features/outbound"
|
"github.com/xtls/xray-core/features/outbound"
|
||||||
"github.com/xtls/xray-core/proxy/freedom"
|
"github.com/xtls/xray-core/proxy/freedom"
|
||||||
"github.com/xtls/xray-core/transport/internet"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestInterfaces(t *testing.T) {
|
func TestInterfaces(t *testing.T) {
|
||||||
@@ -44,9 +45,9 @@ func TestOutboundWithoutStatCounter(t *testing.T) {
|
|||||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
|
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
|
||||||
})
|
})
|
||||||
conn, _ := h.(*Handler).Dial(ctx, net.TCPDestination(net.DomainAddress("localhost"), 13146))
|
conn, _ := h.(*Handler).Dial(ctx, net.TCPDestination(net.DomainAddress("localhost"), 13146))
|
||||||
_, ok := conn.(*internet.StatCouterConnection)
|
_, ok := conn.(*stat.CounterConnection)
|
||||||
if ok {
|
if ok {
|
||||||
t.Errorf("Expected conn to not be StatCouterConnection")
|
t.Errorf("Expected conn to not be CounterConnection")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,8 +74,8 @@ func TestOutboundWithStatCounter(t *testing.T) {
|
|||||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
|
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
|
||||||
})
|
})
|
||||||
conn, _ := h.(*Handler).Dial(ctx, net.TCPDestination(net.DomainAddress("localhost"), 13146))
|
conn, _ := h.(*Handler).Dial(ctx, net.TCPDestination(net.DomainAddress("localhost"), 13146))
|
||||||
_, ok := conn.(*internet.StatCouterConnection)
|
_, ok := conn.(*stat.CounterConnection)
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Errorf("Expected conn to be StatCouterConnection")
|
t.Errorf("Expected conn to be CounterConnection")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,6 +5,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/mux"
|
"github.com/xtls/xray-core/common/mux"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/session"
|
"github.com/xtls/xray-core/common/session"
|
||||||
|
@@ -1,13 +1,12 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.25.0
|
// protoc-gen-go v1.27.1
|
||||||
// protoc v3.14.0
|
// protoc v3.18.0
|
||||||
// source: app/reverse/config.proto
|
// source: app/reverse/config.proto
|
||||||
|
|
||||||
package reverse
|
package reverse
|
||||||
|
|
||||||
import (
|
import (
|
||||||
proto "github.com/golang/protobuf/proto"
|
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
@@ -21,10 +20,6 @@ const (
|
|||||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
_ = 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
|
type Control_State int32
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@@ -6,6 +6,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/buf"
|
"github.com/xtls/xray-core/common/buf"
|
||||||
"github.com/xtls/xray-core/common/mux"
|
"github.com/xtls/xray-core/common/mux"
|
||||||
|
@@ -9,8 +9,7 @@ type BalancingStrategy interface {
|
|||||||
PickOutbound([]string) string
|
PickOutbound([]string) string
|
||||||
}
|
}
|
||||||
|
|
||||||
type RandomStrategy struct {
|
type RandomStrategy struct{}
|
||||||
}
|
|
||||||
|
|
||||||
func (s *RandomStrategy) PickOutbound(tags []string) string {
|
func (s *RandomStrategy) PickOutbound(tags []string) string {
|
||||||
n := len(tags)
|
n := len(tags)
|
||||||
|
@@ -1,13 +1,12 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.25.0
|
// protoc-gen-go v1.27.1
|
||||||
// protoc v3.14.0
|
// protoc v3.18.0
|
||||||
// source: app/router/command/command.proto
|
// source: app/router/command/command.proto
|
||||||
|
|
||||||
package command
|
package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
proto "github.com/golang/protobuf/proto"
|
|
||||||
net "github.com/xtls/xray-core/common/net"
|
net "github.com/xtls/xray-core/common/net"
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
@@ -22,10 +21,6 @@ const (
|
|||||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
_ = 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.
|
// RoutingContext is the context with information relative to routing process.
|
||||||
// It conforms to the structure of xray.features.routing.Context and
|
// It conforms to the structure of xray.features.routing.Context and
|
||||||
// xray.features.routing.Route.
|
// xray.features.routing.Route.
|
||||||
@@ -91,7 +86,7 @@ func (x *RoutingContext) GetNetwork() net.Network {
|
|||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Network
|
return x.Network
|
||||||
}
|
}
|
||||||
return net.Network_Unknown
|
return net.Network(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *RoutingContext) GetSourceIPs() [][]byte {
|
func (x *RoutingContext) GetSourceIPs() [][]byte {
|
||||||
|
@@ -8,6 +8,9 @@ import (
|
|||||||
"github.com/golang/mock/gomock"
|
"github.com/golang/mock/gomock"
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/google/go-cmp/cmp/cmpopts"
|
"github.com/google/go-cmp/cmp/cmpopts"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/test/bufconn"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/app/router"
|
"github.com/xtls/xray-core/app/router"
|
||||||
. "github.com/xtls/xray-core/app/router/command"
|
. "github.com/xtls/xray-core/app/router/command"
|
||||||
"github.com/xtls/xray-core/app/stats"
|
"github.com/xtls/xray-core/app/stats"
|
||||||
@@ -15,8 +18,6 @@ import (
|
|||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/features/routing"
|
"github.com/xtls/xray-core/features/routing"
|
||||||
"github.com/xtls/xray-core/testing/mocks"
|
"github.com/xtls/xray-core/testing/mocks"
|
||||||
"google.golang.org/grpc"
|
|
||||||
"google.golang.org/grpc/test/bufconn"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestServiceSubscribeRoutingStats(t *testing.T) {
|
func TestServiceSubscribeRoutingStats(t *testing.T) {
|
||||||
@@ -44,7 +45,6 @@ func TestServiceSubscribeRoutingStats(t *testing.T) {
|
|||||||
{SourceIPs: [][]byte{{127, 0, 0, 1}}, Attributes: map[string]string{"attr": "value"}, OutboundTag: "out"},
|
{SourceIPs: [][]byte{{127, 0, 0, 1}}, Attributes: map[string]string{"attr": "value"}, OutboundTag: "out"},
|
||||||
}
|
}
|
||||||
errCh := make(chan error)
|
errCh := make(chan error)
|
||||||
nextPub := make(chan struct{})
|
|
||||||
|
|
||||||
// Server goroutine
|
// Server goroutine
|
||||||
go func() {
|
go func() {
|
||||||
@@ -76,13 +76,6 @@ func TestServiceSubscribeRoutingStats(t *testing.T) {
|
|||||||
if err := publishTestCases(); err != nil {
|
if err := publishTestCases(); err != nil {
|
||||||
errCh <- err
|
errCh <- err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for next round of publishing
|
|
||||||
<-nextPub
|
|
||||||
|
|
||||||
if err := publishTestCases(); err != nil {
|
|
||||||
errCh <- err
|
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Client goroutine
|
// Client goroutine
|
||||||
@@ -144,6 +137,92 @@ func TestServiceSubscribeRoutingStats(t *testing.T) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := testRetrievingAllFields(); 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 TestServiceSubscribeSubsetOfFields(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)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// 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 only a subset of fields
|
// Test retrieving only a subset of fields
|
||||||
testRetrievingSubsetOfFields := func() error {
|
testRetrievingSubsetOfFields := func() error {
|
||||||
streamCtx, streamClose := context.WithCancel(context.Background())
|
streamCtx, streamClose := context.WithCancel(context.Background())
|
||||||
@@ -155,9 +234,6 @@ func TestServiceSubscribeRoutingStats(t *testing.T) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send nextPub signal to start next round of publishing
|
|
||||||
close(nextPub)
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
msg, err := stream.Recv()
|
msg, err := stream.Recv()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -179,10 +255,6 @@ func TestServiceSubscribeRoutingStats(t *testing.T) {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := testRetrievingAllFields(); err != nil {
|
|
||||||
errCh <- err
|
|
||||||
}
|
|
||||||
if err := testRetrievingSubsetOfFields(); err != nil {
|
if err := testRetrievingSubsetOfFields(); err != nil {
|
||||||
errCh <- err
|
errCh <- err
|
||||||
}
|
}
|
||||||
|
@@ -28,6 +28,13 @@ func (c routingContext) GetTargetPort() net.Port {
|
|||||||
return net.Port(c.RoutingContext.GetTargetPort())
|
return net.Port(c.RoutingContext.GetTargetPort())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetSkipDNSResolve is a mock implementation here to match the interface,
|
||||||
|
// SkipDNSResolve is set from dns module, no use if coming from a protobuf object?
|
||||||
|
// TODO: please confirm @Vigilans
|
||||||
|
func (c routingContext) GetSkipDNSResolve() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// AsRoutingContext converts a protobuf RoutingContext into an implementation of routing.Context.
|
// AsRoutingContext converts a protobuf RoutingContext into an implementation of routing.Context.
|
||||||
func AsRoutingContext(r *RoutingContext) routing.Context {
|
func AsRoutingContext(r *RoutingContext) routing.Context {
|
||||||
return routingContext{r}
|
return routingContext{r}
|
||||||
|
@@ -186,6 +186,4 @@ func (c *GeoIPMatcherContainer) Add(geoip *GeoIP) (*GeoIPMatcher, error) {
|
|||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var globalGeoIPContainer GeoIPMatcherContainer
|
||||||
globalGeoIPContainer GeoIPMatcherContainer
|
|
||||||
)
|
|
||||||
|
@@ -6,6 +6,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/app/router"
|
"github.com/xtls/xray-core/app/router"
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
@@ -88,7 +89,8 @@ func TestGeoIPMatcher(t *testing.T) {
|
|||||||
{
|
{
|
||||||
Input: "192.0.1.0",
|
Input: "192.0.1.0",
|
||||||
Output: false,
|
Output: false,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
Input: "0.1.0.0",
|
Input: "0.1.0.0",
|
||||||
Output: true,
|
Output: true,
|
||||||
},
|
},
|
||||||
|
@@ -84,7 +84,6 @@ func (rr *RoutingRule) BuildCondition() (Condition, error) {
|
|||||||
newError("MphDomainMatcher is enabled for ", len(rr.Domain), " domain rule(s)").AtDebug().WriteToLog()
|
newError("MphDomainMatcher is enabled for ", len(rr.Domain), " domain rule(s)").AtDebug().WriteToLog()
|
||||||
conds.Add(matcher)
|
conds.Add(matcher)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(rr.UserEmail) > 0 {
|
if len(rr.UserEmail) > 0 {
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.26.0
|
// protoc-gen-go v1.27.1
|
||||||
// protoc v3.15.8
|
// protoc v3.18.0
|
||||||
// source: app/router/config.proto
|
// source: app/router/config.proto
|
||||||
|
|
||||||
package router
|
package router
|
||||||
|
@@ -80,7 +80,12 @@ func (r *Router) PickRoute(ctx routing.Context) (routing.Route, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) pickRouteInternal(ctx routing.Context) (*Rule, routing.Context, error) {
|
func (r *Router) pickRouteInternal(ctx routing.Context) (*Rule, routing.Context, error) {
|
||||||
if r.domainStrategy == Config_IpOnDemand {
|
// SkipDNSResolve is set from DNS module.
|
||||||
|
// the DOH remote server maybe a domain name,
|
||||||
|
// this prevents cycle resolving dead loop
|
||||||
|
skipDNSResolve := ctx.GetSkipDNSResolve()
|
||||||
|
|
||||||
|
if r.domainStrategy == Config_IpOnDemand && !skipDNSResolve {
|
||||||
ctx = routing_dns.ContextWithDNSClient(ctx, r.dns)
|
ctx = routing_dns.ContextWithDNSClient(ctx, r.dns)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +95,7 @@ func (r *Router) pickRouteInternal(ctx routing.Context) (*Rule, routing.Context,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.domainStrategy != Config_IpIfNonMatch || len(ctx.GetTargetDomain()) == 0 {
|
if r.domainStrategy != Config_IpIfNonMatch || len(ctx.GetTargetDomain()) == 0 || skipDNSResolve {
|
||||||
return nil, ctx, common.ErrNoClue
|
return nil, ctx, common.ErrNoClue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -4,12 +4,14 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/features/dns"
|
||||||
|
|
||||||
"github.com/golang/mock/gomock"
|
"github.com/golang/mock/gomock"
|
||||||
|
|
||||||
. "github.com/xtls/xray-core/app/router"
|
. "github.com/xtls/xray-core/app/router"
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/session"
|
"github.com/xtls/xray-core/common/session"
|
||||||
"github.com/xtls/xray-core/features/dns"
|
|
||||||
"github.com/xtls/xray-core/features/outbound"
|
"github.com/xtls/xray-core/features/outbound"
|
||||||
routing_session "github.com/xtls/xray-core/features/routing/session"
|
routing_session "github.com/xtls/xray-core/features/routing/session"
|
||||||
"github.com/xtls/xray-core/testing/mocks"
|
"github.com/xtls/xray-core/testing/mocks"
|
||||||
|
@@ -1,13 +1,12 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.25.0
|
// protoc-gen-go v1.27.1
|
||||||
// protoc v3.14.0
|
// protoc v3.18.0
|
||||||
// source: app/stats/command/command.proto
|
// source: app/stats/command/command.proto
|
||||||
|
|
||||||
package command
|
package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
proto "github.com/golang/protobuf/proto"
|
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
@@ -21,10 +20,6 @@ const (
|
|||||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
_ = 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 {
|
type GetStatsRequest struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
|
@@ -1,13 +1,12 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.25.0
|
// protoc-gen-go v1.27.1
|
||||||
// protoc v3.14.0
|
// protoc v3.18.0
|
||||||
// source: app/stats/config.proto
|
// source: app/stats/config.proto
|
||||||
|
|
||||||
package stats
|
package stats
|
||||||
|
|
||||||
import (
|
import (
|
||||||
proto "github.com/golang/protobuf/proto"
|
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
@@ -21,10 +20,6 @@ const (
|
|||||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
_ = 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 {
|
type Config struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
|
@@ -6,6 +6,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
. "github.com/xtls/xray-core/common/buf"
|
. "github.com/xtls/xray-core/common/buf"
|
||||||
)
|
)
|
||||||
@@ -77,6 +78,7 @@ func TestBufferByte(t *testing.T) {
|
|||||||
buffer.Release()
|
buffer.Release()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBufferResize(t *testing.T) {
|
func TestBufferResize(t *testing.T) {
|
||||||
buffer := New()
|
buffer := New()
|
||||||
defer buffer.Release()
|
defer buffer.Release()
|
||||||
|
@@ -6,6 +6,9 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/features/stats"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/stat"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Reader extends io.Reader with MultiBuffer.
|
// Reader extends io.Reader with MultiBuffer.
|
||||||
@@ -29,9 +32,17 @@ type Writer interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WriteAllBytes ensures all bytes are written into the given writer.
|
// WriteAllBytes ensures all bytes are written into the given writer.
|
||||||
func WriteAllBytes(writer io.Writer, payload []byte) error {
|
func WriteAllBytes(writer io.Writer, payload []byte, c stats.Counter) error {
|
||||||
|
wc := 0
|
||||||
|
defer func() {
|
||||||
|
if c != nil {
|
||||||
|
c.Add(int64(wc))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
for len(payload) > 0 {
|
for len(payload) > 0 {
|
||||||
n, err := writer.Write(payload)
|
n, err := writer.Write(payload)
|
||||||
|
wc += n
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -65,7 +76,13 @@ func NewReader(reader io.Reader) Reader {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
newError("failed to get sysconn").Base(err).WriteToLog()
|
newError("failed to get sysconn").Base(err).WriteToLog()
|
||||||
} else {
|
} else {
|
||||||
return NewReadVReader(reader, rawConn)
|
var counter stats.Counter
|
||||||
|
|
||||||
|
if statConn, ok := reader.(*stat.CounterConnection); ok {
|
||||||
|
reader = statConn.Connection
|
||||||
|
counter = statConn.ReadCounter
|
||||||
|
}
|
||||||
|
return NewReadVReader(reader, rawConn, counter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -104,13 +121,24 @@ func NewWriter(writer io.Writer) Writer {
|
|||||||
return mw
|
return mw
|
||||||
}
|
}
|
||||||
|
|
||||||
if isPacketWriter(writer) {
|
iConn := writer
|
||||||
|
if statConn, ok := writer.(*stat.CounterConnection); ok {
|
||||||
|
iConn = statConn.Connection
|
||||||
|
}
|
||||||
|
|
||||||
|
if isPacketWriter(iConn) {
|
||||||
return &SequentialWriter{
|
return &SequentialWriter{
|
||||||
Writer: writer,
|
Writer: writer,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var counter stats.Counter
|
||||||
|
|
||||||
|
if statConn, ok := writer.(*stat.CounterConnection); ok {
|
||||||
|
counter = statConn.WriteCounter
|
||||||
|
}
|
||||||
return &BufferToBytesWriter{
|
return &BufferToBytesWriter{
|
||||||
Writer: writer,
|
Writer: iConn,
|
||||||
|
counter: counter,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,11 +4,11 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
. "github.com/xtls/xray-core/common/buf"
|
. "github.com/xtls/xray-core/common/buf"
|
||||||
)
|
)
|
||||||
@@ -119,7 +119,7 @@ func TestMultiBufferReadAllToByte(t *testing.T) {
|
|||||||
common.Must(err)
|
common.Must(err)
|
||||||
f.Close()
|
f.Close()
|
||||||
|
|
||||||
cnt, err := ioutil.ReadFile(dat)
|
cnt, err := os.ReadFile(dat)
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
if d := cmp.Diff(buf2, cnt); d != "" {
|
if d := cmp.Diff(buf2, cnt); d != "" {
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
// +build !windows
|
//go:build !windows && !wasm && !illumos
|
||||||
// +build !wasm
|
// +build !windows,!wasm,!illumos
|
||||||
// +build !illumos
|
|
||||||
|
|
||||||
package buf
|
package buf
|
||||||
|
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
//go:build !wasm
|
||||||
// +build !wasm
|
// +build !wasm
|
||||||
|
|
||||||
package buf
|
package buf
|
||||||
@@ -6,6 +7,8 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/features/stats"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/platform"
|
"github.com/xtls/xray-core/common/platform"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -53,17 +56,19 @@ type ReadVReader struct {
|
|||||||
rawConn syscall.RawConn
|
rawConn syscall.RawConn
|
||||||
mr multiReader
|
mr multiReader
|
||||||
alloc allocStrategy
|
alloc allocStrategy
|
||||||
|
counter stats.Counter
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewReadVReader creates a new ReadVReader.
|
// NewReadVReader creates a new ReadVReader.
|
||||||
func NewReadVReader(reader io.Reader, rawConn syscall.RawConn) *ReadVReader {
|
func NewReadVReader(reader io.Reader, rawConn syscall.RawConn, counter stats.Counter) *ReadVReader {
|
||||||
return &ReadVReader{
|
return &ReadVReader{
|
||||||
Reader: reader,
|
Reader: reader,
|
||||||
rawConn: rawConn,
|
rawConn: rawConn,
|
||||||
alloc: allocStrategy{
|
alloc: allocStrategy{
|
||||||
current: 1,
|
current: 1,
|
||||||
},
|
},
|
||||||
mr: newMultiReader(),
|
mr: newMultiReader(),
|
||||||
|
counter: counter,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,10 +127,16 @@ func (r *ReadVReader) ReadMultiBuffer() (MultiBuffer, error) {
|
|||||||
if b.IsFull() {
|
if b.IsFull() {
|
||||||
r.alloc.Adjust(1)
|
r.alloc.Adjust(1)
|
||||||
}
|
}
|
||||||
|
if r.counter != nil && b != nil {
|
||||||
|
r.counter.Add(int64(b.Len()))
|
||||||
|
}
|
||||||
return MultiBuffer{b}, err
|
return MultiBuffer{b}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
mb, err := r.readMulti()
|
mb, err := r.readMulti()
|
||||||
|
if r.counter != nil && mb != nil {
|
||||||
|
r.counter.Add(int64(mb.Len()))
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
//go:build wasm
|
||||||
// +build wasm
|
// +build wasm
|
||||||
|
|
||||||
package buf
|
package buf
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
//go:build !wasm
|
||||||
// +build !wasm
|
// +build !wasm
|
||||||
|
|
||||||
package buf_test
|
package buf_test
|
||||||
@@ -9,10 +10,11 @@ import (
|
|||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
|
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
. "github.com/xtls/xray-core/common/buf"
|
. "github.com/xtls/xray-core/common/buf"
|
||||||
"github.com/xtls/xray-core/testing/servers/tcp"
|
"github.com/xtls/xray-core/testing/servers/tcp"
|
||||||
"golang.org/x/sync/errgroup"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestReadvReader(t *testing.T) {
|
func TestReadvReader(t *testing.T) {
|
||||||
@@ -50,7 +52,7 @@ func TestReadvReader(t *testing.T) {
|
|||||||
rawConn, err := conn.(*net.TCPConn).SyscallConn()
|
rawConn, err := conn.(*net.TCPConn).SyscallConn()
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
reader := NewReadVReader(conn, rawConn)
|
reader := NewReadVReader(conn, rawConn, nil)
|
||||||
var rmb MultiBuffer
|
var rmb MultiBuffer
|
||||||
for {
|
for {
|
||||||
mb, err := reader.ReadMultiBuffer()
|
mb, err := reader.ReadMultiBuffer()
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
//go:build illumos
|
||||||
// +build illumos
|
// +build illumos
|
||||||
|
|
||||||
package buf
|
package buf
|
||||||
|
@@ -5,6 +5,8 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/features/stats"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
)
|
)
|
||||||
@@ -13,7 +15,8 @@ import (
|
|||||||
type BufferToBytesWriter struct {
|
type BufferToBytesWriter struct {
|
||||||
io.Writer
|
io.Writer
|
||||||
|
|
||||||
cache [][]byte
|
counter stats.Counter
|
||||||
|
cache [][]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteMultiBuffer implements Writer. This method takes ownership of the given buffer.
|
// WriteMultiBuffer implements Writer. This method takes ownership of the given buffer.
|
||||||
@@ -26,7 +29,7 @@ func (w *BufferToBytesWriter) WriteMultiBuffer(mb MultiBuffer) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(mb) == 1 {
|
if len(mb) == 1 {
|
||||||
return WriteAllBytes(w.Writer, mb[0].Bytes())
|
return WriteAllBytes(w.Writer, mb[0].Bytes(), w.counter)
|
||||||
}
|
}
|
||||||
|
|
||||||
if cap(w.cache) < len(mb) {
|
if cap(w.cache) < len(mb) {
|
||||||
@@ -45,9 +48,15 @@ func (w *BufferToBytesWriter) WriteMultiBuffer(mb MultiBuffer) error {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
nb := net.Buffers(bs)
|
nb := net.Buffers(bs)
|
||||||
|
wc := int64(0)
|
||||||
|
defer func() {
|
||||||
|
if w.counter != nil {
|
||||||
|
w.counter.Add(wc)
|
||||||
|
}
|
||||||
|
}()
|
||||||
for size > 0 {
|
for size > 0 {
|
||||||
n, err := nb.WriteTo(w.Writer)
|
n, err := nb.WriteTo(w.Writer)
|
||||||
|
wc += n
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -173,7 +182,7 @@ func (w *BufferedWriter) flushInternal() error {
|
|||||||
w.buffer = nil
|
w.buffer = nil
|
||||||
|
|
||||||
if writer, ok := w.writer.(io.Writer); ok {
|
if writer, ok := w.writer.(io.Writer); ok {
|
||||||
err := WriteAllBytes(writer, b.Bytes())
|
err := WriteAllBytes(writer, b.Bytes(), nil)
|
||||||
b.Release()
|
b.Release()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
38
common/cache/lru.go
vendored
38
common/cache/lru.go
vendored
@@ -2,7 +2,7 @@ package cache
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"container/list"
|
"container/list"
|
||||||
sync "sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Lru simple, fast lru cache implementation
|
// Lru simple, fast lru cache implementation
|
||||||
@@ -28,7 +28,7 @@ type lruElement struct {
|
|||||||
|
|
||||||
// NewLru init a lru cache
|
// NewLru init a lru cache
|
||||||
func NewLru(cap int) Lru {
|
func NewLru(cap int) Lru {
|
||||||
return lru{
|
return &lru{
|
||||||
capacity: cap,
|
capacity: cap,
|
||||||
doubleLinkedlist: list.New(),
|
doubleLinkedlist: list.New(),
|
||||||
keyToElement: new(sync.Map),
|
keyToElement: new(sync.Map),
|
||||||
@@ -37,49 +37,53 @@ func NewLru(cap int) Lru {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l lru) Get(key interface{}) (value interface{}, ok bool) {
|
func (l *lru) Get(key interface{}) (value interface{}, ok bool) {
|
||||||
|
l.mu.Lock()
|
||||||
|
defer l.mu.Unlock()
|
||||||
if v, ok := l.keyToElement.Load(key); ok {
|
if v, ok := l.keyToElement.Load(key); ok {
|
||||||
element := v.(*list.Element)
|
element := v.(*list.Element)
|
||||||
l.doubleLinkedlist.MoveBefore(element, l.doubleLinkedlist.Front())
|
l.doubleLinkedlist.MoveToFront(element)
|
||||||
return element.Value.(lruElement).value, true
|
return element.Value.(*lruElement).value, true
|
||||||
}
|
}
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l lru) GetKeyFromValue(value interface{}) (key interface{}, ok bool) {
|
func (l *lru) GetKeyFromValue(value interface{}) (key interface{}, ok bool) {
|
||||||
|
l.mu.Lock()
|
||||||
|
defer l.mu.Unlock()
|
||||||
if k, ok := l.valueToElement.Load(value); ok {
|
if k, ok := l.valueToElement.Load(value); ok {
|
||||||
element := k.(*list.Element)
|
element := k.(*list.Element)
|
||||||
l.doubleLinkedlist.MoveBefore(element, l.doubleLinkedlist.Front())
|
l.doubleLinkedlist.MoveToFront(element)
|
||||||
return element.Value.(lruElement).key, true
|
return element.Value.(*lruElement).key, true
|
||||||
}
|
}
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l lru) PeekKeyFromValue(value interface{}) (key interface{}, ok bool) {
|
func (l *lru) PeekKeyFromValue(value interface{}) (key interface{}, ok bool) {
|
||||||
if k, ok := l.valueToElement.Load(value); ok {
|
if k, ok := l.valueToElement.Load(value); ok {
|
||||||
element := k.(*list.Element)
|
element := k.(*list.Element)
|
||||||
return element.Value.(lruElement).key, true
|
return element.Value.(*lruElement).key, true
|
||||||
}
|
}
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l lru) Put(key, value interface{}) {
|
func (l *lru) Put(key, value interface{}) {
|
||||||
e := lruElement{key, value}
|
l.mu.Lock()
|
||||||
|
e := &lruElement{key, value}
|
||||||
if v, ok := l.keyToElement.Load(key); ok {
|
if v, ok := l.keyToElement.Load(key); ok {
|
||||||
element := v.(*list.Element)
|
element := v.(*list.Element)
|
||||||
element.Value = e
|
element.Value = e
|
||||||
l.doubleLinkedlist.MoveBefore(element, l.doubleLinkedlist.Front())
|
l.doubleLinkedlist.MoveToFront(element)
|
||||||
} else {
|
} else {
|
||||||
l.mu.Lock()
|
|
||||||
element := l.doubleLinkedlist.PushFront(e)
|
element := l.doubleLinkedlist.PushFront(e)
|
||||||
l.keyToElement.Store(key, element)
|
l.keyToElement.Store(key, element)
|
||||||
l.valueToElement.Store(value, element)
|
l.valueToElement.Store(value, element)
|
||||||
if l.doubleLinkedlist.Len() > l.capacity {
|
if l.doubleLinkedlist.Len() > l.capacity {
|
||||||
toBeRemove := l.doubleLinkedlist.Back()
|
toBeRemove := l.doubleLinkedlist.Back()
|
||||||
l.doubleLinkedlist.Remove(toBeRemove)
|
l.doubleLinkedlist.Remove(toBeRemove)
|
||||||
l.keyToElement.Delete(toBeRemove.Value.(lruElement).key)
|
l.keyToElement.Delete(toBeRemove.Value.(*lruElement).key)
|
||||||
l.valueToElement.Delete(toBeRemove.Value.(lruElement).value)
|
l.valueToElement.Delete(toBeRemove.Value.(*lruElement).value)
|
||||||
}
|
}
|
||||||
l.mu.Unlock()
|
|
||||||
}
|
}
|
||||||
|
l.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
@@ -5,7 +5,6 @@ package common
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/build"
|
"go/build"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -15,10 +14,8 @@ import (
|
|||||||
|
|
||||||
//go:generate go run github.com/xtls/xray-core/common/errors/errorgen
|
//go:generate go run github.com/xtls/xray-core/common/errors/errorgen
|
||||||
|
|
||||||
var (
|
// ErrNoClue is for the situation that existing information is not enough to make a decision. For example, Router may return this error when there is no suitable route.
|
||||||
// ErrNoClue is for the situation that existing information is not enough to make a decision. For example, Router may return this error when there is no suitable route.
|
var ErrNoClue = errors.New("not enough information for making a decision")
|
||||||
ErrNoClue = errors.New("not enough information for making a decision")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Must panics if err is not nil.
|
// Must panics if err is not nil.
|
||||||
func Must(err error) {
|
func Must(err error) {
|
||||||
@@ -69,7 +66,7 @@ func GetRuntimeEnv(key string) (string, error) {
|
|||||||
}
|
}
|
||||||
var data []byte
|
var data []byte
|
||||||
var runtimeEnv string
|
var runtimeEnv string
|
||||||
data, readErr := ioutil.ReadFile(file)
|
data, readErr := os.ReadFile(file)
|
||||||
if readErr != nil {
|
if readErr != nil {
|
||||||
return "", readErr
|
return "", readErr
|
||||||
}
|
}
|
||||||
@@ -131,7 +128,7 @@ func GetModuleName(pathToProjectRoot string) (string, error) {
|
|||||||
for {
|
for {
|
||||||
if idx := strings.LastIndex(loopPath, string(filepath.Separator)); idx >= 0 {
|
if idx := strings.LastIndex(loopPath, string(filepath.Separator)); idx >= 0 {
|
||||||
gomodPath := filepath.Join(loopPath, "go.mod")
|
gomodPath := filepath.Join(loopPath, "go.mod")
|
||||||
gomodBytes, err := ioutil.ReadFile(gomodPath)
|
gomodBytes, err := os.ReadFile(gomodPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
loopPath = loopPath[:idx]
|
loopPath = loopPath[:idx]
|
||||||
continue
|
continue
|
||||||
|
@@ -290,7 +290,6 @@ func (w *AuthenticationWriter) writeStream(mb buf.MultiBuffer) error {
|
|||||||
mb = nb
|
mb = nb
|
||||||
|
|
||||||
eb, err := w.seal(rawBytes[:nBytes])
|
eb, err := w.seal(rawBytes[:nBytes])
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
buf.ReleaseMulti(mb2Write)
|
buf.ReleaseMulti(mb2Write)
|
||||||
return err
|
return err
|
||||||
|
@@ -18,7 +18,7 @@ func mustDecodeHex(s string) []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestChaCha20Stream(t *testing.T) {
|
func TestChaCha20Stream(t *testing.T) {
|
||||||
var cases = []struct {
|
cases := []struct {
|
||||||
key []byte
|
key []byte
|
||||||
iv []byte
|
iv []byte
|
||||||
output []byte
|
output []byte
|
||||||
|
@@ -3,7 +3,7 @@ package internal
|
|||||||
import "encoding/binary"
|
import "encoding/binary"
|
||||||
|
|
||||||
func ChaCha20Block(s *[16]uint32, out []byte, rounds int) {
|
func ChaCha20Block(s *[16]uint32, out []byte, rounds int) {
|
||||||
var x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15 = s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7], s[8], s[9], s[10], s[11], s[12], s[13], s[14], s[15]
|
x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15 := s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7], s[8], s[9], s[10], s[11], s[12], s[13], s[14], s[15]
|
||||||
for i := 0; i < rounds; i += 2 {
|
for i := 0; i < rounds; i += 2 {
|
||||||
var x uint32
|
var x uint32
|
||||||
|
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
//go:build generate
|
||||||
// +build generate
|
// +build generate
|
||||||
|
|
||||||
package main
|
package main
|
||||||
@@ -55,7 +56,7 @@ func ChaCha20Block(s *[16]uint32, out []byte, rounds int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
file, err := os.OpenFile("chacha_core.generated.go", os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644)
|
file, err := os.OpenFile("chacha_core.generated.go", os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0o644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to generate chacha_core.go: %v", err)
|
log.Fatalf("Failed to generate chacha_core.go: %v", err)
|
||||||
}
|
}
|
||||||
|
@@ -27,9 +27,7 @@ func (r *CryptionReader) Read(data []byte) (int, error) {
|
|||||||
return nBytes, err
|
return nBytes, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var _ buf.Writer = (*CryptionWriter)(nil)
|
||||||
_ buf.Writer = (*CryptionWriter)(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
type CryptionWriter struct {
|
type CryptionWriter struct {
|
||||||
stream cipher.Stream
|
stream cipher.Stream
|
||||||
@@ -50,7 +48,7 @@ func NewCryptionWriter(stream cipher.Stream, writer io.Writer) *CryptionWriter {
|
|||||||
func (w *CryptionWriter) Write(data []byte) (int, error) {
|
func (w *CryptionWriter) Write(data []byte) (int, error) {
|
||||||
w.stream.XORKeyStream(data, data)
|
w.stream.XORKeyStream(data, data)
|
||||||
|
|
||||||
if err := buf.WriteAllBytes(w.writer, data); err != nil {
|
if err := buf.WriteAllBytes(w.writer, data, nil); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
return len(data), nil
|
return len(data), nil
|
||||||
|
@@ -2,11 +2,8 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -20,26 +17,21 @@ func main() {
|
|||||||
pkg = "core"
|
pkg = "core"
|
||||||
}
|
}
|
||||||
|
|
||||||
moduleName, gmnErr := common.GetModuleName(pwd)
|
file, err := os.OpenFile("errors.generated.go", os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0o644)
|
||||||
if gmnErr != nil {
|
|
||||||
fmt.Println("can not get module path", gmnErr)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
file, err := os.OpenFile("errors.generated.go", os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to generate errors.generated.go: %v", err)
|
fmt.Printf("Failed to generate errors.generated.go: %v", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
fmt.Fprintln(file, "package", pkg)
|
fmt.Fprintf(file, `package %s
|
||||||
fmt.Fprintln(file, "")
|
|
||||||
fmt.Fprintln(file, "import \""+moduleName+"/common/errors\"")
|
import "github.com/xtls/xray-core/common/errors"
|
||||||
fmt.Fprintln(file, "")
|
|
||||||
fmt.Fprintln(file, "type errPathObjHolder struct{}")
|
type errPathObjHolder struct{}
|
||||||
fmt.Fprintln(file, "")
|
|
||||||
fmt.Fprintln(file, "func newError(values ...interface{}) *errors.Error {")
|
func newError(values ...interface{}) *errors.Error {
|
||||||
fmt.Fprintln(file, " return errors.New(values...).WithPathObj(errPathObjHolder{})")
|
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
||||||
fmt.Fprintln(file, "}")
|
}
|
||||||
|
`, pkg)
|
||||||
}
|
}
|
||||||
|
@@ -32,9 +32,7 @@ func Record(msg Message) {
|
|||||||
logHandler.Handle(msg)
|
logHandler.Handle(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var logHandler syncHandler
|
||||||
logHandler syncHandler
|
|
||||||
)
|
|
||||||
|
|
||||||
// RegisterHandler register a new handler as current log handler. Previous registered handler will be discarded.
|
// RegisterHandler register a new handler as current log handler. Previous registered handler will be discarded.
|
||||||
func RegisterHandler(handler Handler) {
|
func RegisterHandler(handler Handler) {
|
||||||
|
@@ -1,13 +1,12 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.25.0
|
// protoc-gen-go v1.27.1
|
||||||
// protoc v3.14.0
|
// protoc v3.18.0
|
||||||
// source: common/log/log.proto
|
// source: common/log/log.proto
|
||||||
|
|
||||||
package log
|
package log
|
||||||
|
|
||||||
import (
|
import (
|
||||||
proto "github.com/golang/protobuf/proto"
|
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
@@ -21,10 +20,6 @@ const (
|
|||||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
_ = 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 Severity int32
|
type Severity int32
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@@ -130,13 +130,13 @@ func CreateStderrLogWriter() WriterCreator {
|
|||||||
|
|
||||||
// CreateFileLogWriter returns a LogWriterCreator that creates LogWriter for the given file.
|
// CreateFileLogWriter returns a LogWriterCreator that creates LogWriter for the given file.
|
||||||
func CreateFileLogWriter(path string) (WriterCreator, error) {
|
func CreateFileLogWriter(path string) (WriterCreator, error) {
|
||||||
file, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
|
file, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0o600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
file.Close()
|
file.Close()
|
||||||
return func() Writer {
|
return func() Writer {
|
||||||
file, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
|
file, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0o600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
package log_test
|
package log_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -13,7 +12,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestFileLogger(t *testing.T) {
|
func TestFileLogger(t *testing.T) {
|
||||||
f, err := ioutil.TempFile("", "vtest")
|
f, err := os.CreateTemp("", "vtest")
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
path := f.Name()
|
path := f.Name()
|
||||||
common.Must(f.Close())
|
common.Must(f.Close())
|
||||||
|
@@ -142,7 +142,6 @@ func (f *DialingWorkerFactory) Create() (*ClientWorker, error) {
|
|||||||
Reader: downlinkReader,
|
Reader: downlinkReader,
|
||||||
Writer: upLinkWriter,
|
Writer: upLinkWriter,
|
||||||
}, f.Strategy)
|
}, f.Strategy)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -175,8 +174,10 @@ type ClientWorker struct {
|
|||||||
strategy ClientStrategy
|
strategy ClientStrategy
|
||||||
}
|
}
|
||||||
|
|
||||||
var muxCoolAddress = net.DomainAddress("v1.mux.cool")
|
var (
|
||||||
var muxCoolPort = net.Port(9527)
|
muxCoolAddress = net.DomainAddress("v1.mux.cool")
|
||||||
|
muxCoolPort = net.Port(9527)
|
||||||
|
)
|
||||||
|
|
||||||
// NewClientWorker creates a new mux.Client.
|
// NewClientWorker creates a new mux.Client.
|
||||||
func NewClientWorker(stream transport.Link, s ClientStrategy) (*ClientWorker, error) {
|
func NewClientWorker(stream transport.Link, s ClientStrategy) (*ClientWorker, error) {
|
||||||
|
@@ -6,6 +6,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/golang/mock/gomock"
|
"github.com/golang/mock/gomock"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/mux"
|
"github.com/xtls/xray-core/common/mux"
|
||||||
|
@@ -1,13 +1,12 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.25.0
|
// protoc-gen-go v1.27.1
|
||||||
// protoc v3.14.0
|
// protoc v3.18.0
|
||||||
// source: common/net/address.proto
|
// source: common/net/address.proto
|
||||||
|
|
||||||
package net
|
package net
|
||||||
|
|
||||||
import (
|
import (
|
||||||
proto "github.com/golang/protobuf/proto"
|
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
@@ -21,10 +20,6 @@ const (
|
|||||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
_ = 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
|
|
||||||
|
|
||||||
// Address of a network host. It may be either an IP address or a domain
|
// Address of a network host. It may be either an IP address or a domain
|
||||||
// address.
|
// address.
|
||||||
type IPOrDomain struct {
|
type IPOrDomain struct {
|
||||||
|
@@ -1,13 +1,12 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.25.0
|
// protoc-gen-go v1.27.1
|
||||||
// protoc v3.14.0
|
// protoc v3.18.0
|
||||||
// source: common/net/destination.proto
|
// source: common/net/destination.proto
|
||||||
|
|
||||||
package net
|
package net
|
||||||
|
|
||||||
import (
|
import (
|
||||||
proto "github.com/golang/protobuf/proto"
|
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
@@ -21,10 +20,6 @@ const (
|
|||||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
_ = 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
|
|
||||||
|
|
||||||
// Endpoint of a network connection.
|
// Endpoint of a network connection.
|
||||||
type Endpoint struct {
|
type Endpoint struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
|
@@ -1,13 +1,12 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.25.0
|
// protoc-gen-go v1.27.1
|
||||||
// protoc v3.14.0
|
// protoc v3.18.0
|
||||||
// source: common/net/network.proto
|
// source: common/net/network.proto
|
||||||
|
|
||||||
package net
|
package net
|
||||||
|
|
||||||
import (
|
import (
|
||||||
proto "github.com/golang/protobuf/proto"
|
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
@@ -21,10 +20,6 @@ const (
|
|||||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
_ = 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 Network int32
|
type Network int32
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@@ -1,13 +1,12 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.25.0
|
// protoc-gen-go v1.27.1
|
||||||
// protoc v3.14.0
|
// protoc v3.18.0
|
||||||
// source: common/net/port.proto
|
// source: common/net/port.proto
|
||||||
|
|
||||||
package net
|
package net
|
||||||
|
|
||||||
import (
|
import (
|
||||||
proto "github.com/golang/protobuf/proto"
|
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
@@ -21,10 +20,6 @@ const (
|
|||||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
_ = 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
|
|
||||||
|
|
||||||
// PortRange represents a range of ports.
|
// PortRange represents a range of ports.
|
||||||
type PortRange struct {
|
type PortRange struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
|
@@ -3,17 +3,21 @@ package net
|
|||||||
import "net"
|
import "net"
|
||||||
|
|
||||||
// DialTCP is an alias of net.DialTCP.
|
// DialTCP is an alias of net.DialTCP.
|
||||||
var DialTCP = net.DialTCP
|
var (
|
||||||
var DialUDP = net.DialUDP
|
DialTCP = net.DialTCP
|
||||||
var DialUnix = net.DialUnix
|
DialUDP = net.DialUDP
|
||||||
var Dial = net.Dial
|
DialUnix = net.DialUnix
|
||||||
|
Dial = net.Dial
|
||||||
|
)
|
||||||
|
|
||||||
type ListenConfig = net.ListenConfig
|
type ListenConfig = net.ListenConfig
|
||||||
|
|
||||||
var Listen = net.Listen
|
var (
|
||||||
var ListenTCP = net.ListenTCP
|
Listen = net.Listen
|
||||||
var ListenUDP = net.ListenUDP
|
ListenTCP = net.ListenTCP
|
||||||
var ListenUnix = net.ListenUnix
|
ListenUDP = net.ListenUDP
|
||||||
|
ListenUnix = net.ListenUnix
|
||||||
|
)
|
||||||
|
|
||||||
var LookupIP = net.LookupIP
|
var LookupIP = net.LookupIP
|
||||||
|
|
||||||
@@ -26,36 +30,54 @@ var SplitHostPort = net.SplitHostPort
|
|||||||
|
|
||||||
var CIDRMask = net.CIDRMask
|
var CIDRMask = net.CIDRMask
|
||||||
|
|
||||||
type Addr = net.Addr
|
type (
|
||||||
type Conn = net.Conn
|
Addr = net.Addr
|
||||||
type PacketConn = net.PacketConn
|
Conn = net.Conn
|
||||||
|
PacketConn = net.PacketConn
|
||||||
|
)
|
||||||
|
|
||||||
type TCPAddr = net.TCPAddr
|
type (
|
||||||
type TCPConn = net.TCPConn
|
TCPAddr = net.TCPAddr
|
||||||
|
TCPConn = net.TCPConn
|
||||||
|
)
|
||||||
|
|
||||||
type UDPAddr = net.UDPAddr
|
type (
|
||||||
type UDPConn = net.UDPConn
|
UDPAddr = net.UDPAddr
|
||||||
|
UDPConn = net.UDPConn
|
||||||
|
)
|
||||||
|
|
||||||
type UnixAddr = net.UnixAddr
|
type (
|
||||||
type UnixConn = net.UnixConn
|
UnixAddr = net.UnixAddr
|
||||||
|
UnixConn = net.UnixConn
|
||||||
|
)
|
||||||
|
|
||||||
// IP is an alias for net.IP.
|
// IP is an alias for net.IP.
|
||||||
type IP = net.IP
|
type (
|
||||||
type IPMask = net.IPMask
|
IP = net.IP
|
||||||
type IPNet = net.IPNet
|
IPMask = net.IPMask
|
||||||
|
IPNet = net.IPNet
|
||||||
|
)
|
||||||
|
|
||||||
const IPv4len = net.IPv4len
|
const (
|
||||||
const IPv6len = net.IPv6len
|
IPv4len = net.IPv4len
|
||||||
|
IPv6len = net.IPv6len
|
||||||
|
)
|
||||||
|
|
||||||
type Error = net.Error
|
type (
|
||||||
type AddrError = net.AddrError
|
Error = net.Error
|
||||||
|
AddrError = net.AddrError
|
||||||
|
)
|
||||||
|
|
||||||
type Dialer = net.Dialer
|
type (
|
||||||
type Listener = net.Listener
|
Dialer = net.Dialer
|
||||||
type TCPListener = net.TCPListener
|
Listener = net.Listener
|
||||||
type UnixListener = net.UnixListener
|
TCPListener = net.TCPListener
|
||||||
|
UnixListener = net.UnixListener
|
||||||
|
)
|
||||||
|
|
||||||
var ResolveUnixAddr = net.ResolveUnixAddr
|
var (
|
||||||
var ResolveUDPAddr = net.ResolveUDPAddr
|
ResolveUnixAddr = net.ResolveUnixAddr
|
||||||
|
ResolveUDPAddr = net.ResolveUDPAddr
|
||||||
|
)
|
||||||
|
|
||||||
type Resolver = net.Resolver
|
type Resolver = net.Resolver
|
||||||
|
@@ -4,7 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
@@ -56,7 +56,6 @@ func GetOCSPForCert(cert [][]byte) ([]byte, error) {
|
|||||||
pemBundle := bundle.Bytes()
|
pemBundle := bundle.Bytes()
|
||||||
|
|
||||||
certificates, err := parsePEMBundle(pemBundle)
|
certificates, err := parsePEMBundle(pemBundle)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -74,7 +73,7 @@ func GetOCSPForCert(cert [][]byte) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
issuerBytes, errC := ioutil.ReadAll(resp.Body)
|
issuerBytes, errC := io.ReadAll(resp.Body)
|
||||||
if errC != nil {
|
if errC != nil {
|
||||||
return nil, newError(errC)
|
return nil, newError(errC)
|
||||||
}
|
}
|
||||||
@@ -98,13 +97,11 @@ func GetOCSPForCert(cert [][]byte) ([]byte, error) {
|
|||||||
return nil, newError(err)
|
return nil, newError(err)
|
||||||
}
|
}
|
||||||
defer req.Body.Close()
|
defer req.Body.Close()
|
||||||
ocspResBytes, err := ioutil.ReadAll(req.Body)
|
ocspResBytes, err := io.ReadAll(req.Body)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, newError(err)
|
return nil, newError(err)
|
||||||
}
|
}
|
||||||
return ocspResBytes, nil
|
return ocspResBytes, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// parsePEMBundle parses a certificate bundle from top to bottom and returns
|
// parsePEMBundle parses a certificate bundle from top to bottom and returns
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
//go:build !windows
|
||||||
// +build !windows
|
// +build !windows
|
||||||
|
|
||||||
package ctlcmd
|
package ctlcmd
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
//go:build windows
|
||||||
// +build windows
|
// +build windows
|
||||||
|
|
||||||
package ctlcmd
|
package ctlcmd
|
||||||
|
@@ -33,7 +33,7 @@ func CopyFile(dst string, src string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
f, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY, 0644)
|
f, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY, 0o644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
//go:build !windows
|
||||||
// +build !windows
|
// +build !windows
|
||||||
|
|
||||||
package platform
|
package platform
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
//go:build windows
|
||||||
// +build windows
|
// +build windows
|
||||||
|
|
||||||
package platform
|
package platform
|
||||||
|
@@ -6,8 +6,7 @@ import (
|
|||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SniffHeader struct {
|
type SniffHeader struct{}
|
||||||
}
|
|
||||||
|
|
||||||
func (h *SniffHeader) Protocol() string {
|
func (h *SniffHeader) Protocol() string {
|
||||||
return "bittorrent"
|
return "bittorrent"
|
||||||
|
@@ -4,10 +4,11 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"golang.org/x/net/dns/dnsmessage"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/buf"
|
"github.com/xtls/xray-core/common/buf"
|
||||||
"github.com/xtls/xray-core/common/serial"
|
"github.com/xtls/xray-core/common/serial"
|
||||||
"golang.org/x/net/dns/dnsmessage"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func PackMessage(msg *dnsmessage.Message) (*buf.Buffer, error) {
|
func PackMessage(msg *dnsmessage.Message) (*buf.Buffer, error) {
|
||||||
|
@@ -1,13 +1,12 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.25.0
|
// protoc-gen-go v1.27.1
|
||||||
// protoc v3.15.4
|
// protoc v3.18.0
|
||||||
// source: common/protocol/headers.proto
|
// source: common/protocol/headers.proto
|
||||||
|
|
||||||
package protocol
|
package protocol
|
||||||
|
|
||||||
import (
|
import (
|
||||||
proto "github.com/golang/protobuf/proto"
|
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
@@ -21,10 +20,6 @@ const (
|
|||||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
_ = 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 SecurityType int32
|
type SecurityType int32
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@@ -1,13 +1,12 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.25.0
|
// protoc-gen-go v1.27.1
|
||||||
// protoc v3.14.0
|
// protoc v3.18.0
|
||||||
// source: common/protocol/server_spec.proto
|
// source: common/protocol/server_spec.proto
|
||||||
|
|
||||||
package protocol
|
package protocol
|
||||||
|
|
||||||
import (
|
import (
|
||||||
proto "github.com/golang/protobuf/proto"
|
|
||||||
net "github.com/xtls/xray-core/common/net"
|
net "github.com/xtls/xray-core/common/net"
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
@@ -22,10 +21,6 @@ const (
|
|||||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
_ = 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 ServerEndpoint struct {
|
type ServerEndpoint struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
|
@@ -73,6 +73,7 @@ func printJSON(certificate *Certificate) {
|
|||||||
os.Stdout.Write(content)
|
os.Stdout.Write(content)
|
||||||
os.Stdout.WriteString("\n")
|
os.Stdout.WriteString("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
func printFile(certificate *Certificate, name string) error {
|
func printFile(certificate *Certificate, name string) error {
|
||||||
certPEM, keyPEM := certificate.ToPEM()
|
certPEM, keyPEM := certificate.ToPEM()
|
||||||
return task.Run(context.Background(), func() error {
|
return task.Run(context.Background(), func() error {
|
||||||
@@ -81,6 +82,7 @@ func printFile(certificate *Certificate, name string) error {
|
|||||||
return writeFile(keyPEM, name+"_key.pem")
|
return writeFile(keyPEM, name+"_key.pem")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeFile(content []byte, name string) error {
|
func writeFile(content []byte, name string) error {
|
||||||
f, err := os.Create(name)
|
f, err := os.Create(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user