mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-09-25 08:08:00 +03:00
Compare commits
43
Commits
fkdns
...
close-trace
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d3ebc4239 | ||
|
|
1bdb488c9e | ||
|
|
d38ba9d507 | ||
|
|
9d9eaf399f | ||
|
|
eb32d166af | ||
|
|
1dbafe629a | ||
|
|
c42deab55c | ||
|
|
906d49a271 | ||
|
|
4192ca0827 | ||
|
|
228f1e13aa | ||
|
|
15968585f3 | ||
|
|
4951994ebe | ||
|
|
756a2d1327 | ||
|
|
b279076ba1 | ||
|
|
e61eeae258 | ||
|
|
958eb9ea8f | ||
|
|
8381a5a8a6 | ||
|
|
1ead940a71 | ||
|
|
bdff2fa72e | ||
|
|
1d62941bd2 | ||
|
|
52cf9ef5d6 | ||
|
|
16568314d8 | ||
|
|
1fc6850dc4 | ||
|
|
4e87f59628 | ||
|
|
7ab0a3ccb7 | ||
|
|
2fff03720d | ||
|
|
7f7fc5a829 | ||
|
|
ff6c060168 | ||
|
|
5b552db781 | ||
|
|
108bf7ff82 | ||
|
|
1836b1c6e4 | ||
|
|
b4f08981be | ||
|
|
cd4d0baacd | ||
|
|
3bc24a3d5d | ||
|
|
fa07b34956 | ||
|
|
85a8bf5f39 | ||
|
|
d0f533f94a | ||
|
|
d1db1d6a27 | ||
|
|
1a14ffcec6 | ||
|
|
454c930d13 | ||
|
|
bc590bcb56 | ||
|
|
7cf25970de | ||
|
|
d837687368 |
@@ -32,9 +32,12 @@ RUN echo '{}' >/tmp/usr/local/etc/xray/03_routing.json
|
|||||||
RUN echo '{}' >/tmp/usr/local/etc/xray/04_policy.json
|
RUN echo '{}' >/tmp/usr/local/etc/xray/04_policy.json
|
||||||
RUN echo '{}' >/tmp/usr/local/etc/xray/05_inbounds.json
|
RUN echo '{}' >/tmp/usr/local/etc/xray/05_inbounds.json
|
||||||
RUN echo '{}' >/tmp/usr/local/etc/xray/06_outbounds.json
|
RUN echo '{}' >/tmp/usr/local/etc/xray/06_outbounds.json
|
||||||
RUN echo '{}' >/tmp/usr/local/etc/xray/07_transport.json
|
RUN echo '{}' >/tmp/usr/local/etc/xray/07_stats.json
|
||||||
RUN echo '{}' >/tmp/usr/local/etc/xray/08_stats.json
|
RUN echo '{}' >/tmp/usr/local/etc/xray/08_fakedns.json
|
||||||
RUN echo '{}' >/tmp/usr/local/etc/xray/09_reverse.json
|
RUN echo '{}' >/tmp/usr/local/etc/xray/09_metrics.json
|
||||||
|
RUN echo '{}' >/tmp/usr/local/etc/xray/10_observatory.json
|
||||||
|
RUN echo '{}' >/tmp/usr/local/etc/xray/11_geodata.json
|
||||||
|
RUN echo '{}' >/tmp/usr/local/etc/xray/99_version.json
|
||||||
|
|
||||||
# Create log files
|
# Create log files
|
||||||
RUN mkdir -p /tmp/var/log/xray && touch \
|
RUN mkdir -p /tmp/var/log/xray && touch \
|
||||||
@@ -45,8 +48,8 @@ RUN mkdir -p /tmp/var/log/xray && touch \
|
|||||||
FROM gcr.io/distroless/static:nonroot
|
FROM gcr.io/distroless/static:nonroot
|
||||||
|
|
||||||
COPY --from=build --chown=0:0 --chmod=755 /src/xray /usr/local/bin/xray
|
COPY --from=build --chown=0:0 --chmod=755 /src/xray /usr/local/bin/xray
|
||||||
COPY --from=build --chown=0:0 --chmod=755 /tmp/empty /usr/local/share/xray
|
COPY --from=build --chown=65532:65532 --chmod=755 /tmp/empty /usr/local/share/xray
|
||||||
COPY --from=build --chown=0:0 --chmod=644 /tmp/geodat/*.dat /usr/local/share/xray/
|
COPY --from=build --chown=65532:65532 --chmod=644 /tmp/geodat/*.dat /usr/local/share/xray/
|
||||||
COPY --from=build --chown=0:0 --chmod=755 /tmp/empty /usr/local/etc/xray
|
COPY --from=build --chown=0:0 --chmod=755 /tmp/empty /usr/local/etc/xray
|
||||||
COPY --from=build --chown=0:0 --chmod=644 /tmp/usr/local/etc/xray/*.json /usr/local/etc/xray/
|
COPY --from=build --chown=0:0 --chmod=644 /tmp/usr/local/etc/xray/*.json /usr/local/etc/xray/
|
||||||
COPY --from=build --chown=0:0 --chmod=755 /tmp/empty /var/log/xray
|
COPY --from=build --chown=0:0 --chmod=755 /tmp/empty /var/log/xray
|
||||||
|
|||||||
@@ -32,9 +32,12 @@ RUN echo '{}' >/tmp/usr/local/etc/xray/03_routing.json
|
|||||||
RUN echo '{}' >/tmp/usr/local/etc/xray/04_policy.json
|
RUN echo '{}' >/tmp/usr/local/etc/xray/04_policy.json
|
||||||
RUN echo '{}' >/tmp/usr/local/etc/xray/05_inbounds.json
|
RUN echo '{}' >/tmp/usr/local/etc/xray/05_inbounds.json
|
||||||
RUN echo '{}' >/tmp/usr/local/etc/xray/06_outbounds.json
|
RUN echo '{}' >/tmp/usr/local/etc/xray/06_outbounds.json
|
||||||
RUN echo '{}' >/tmp/usr/local/etc/xray/07_transport.json
|
RUN echo '{}' >/tmp/usr/local/etc/xray/07_stats.json
|
||||||
RUN echo '{}' >/tmp/usr/local/etc/xray/08_stats.json
|
RUN echo '{}' >/tmp/usr/local/etc/xray/08_fakedns.json
|
||||||
RUN echo '{}' >/tmp/usr/local/etc/xray/09_reverse.json
|
RUN echo '{}' >/tmp/usr/local/etc/xray/09_metrics.json
|
||||||
|
RUN echo '{}' >/tmp/usr/local/etc/xray/10_observatory.json
|
||||||
|
RUN echo '{}' >/tmp/usr/local/etc/xray/11_geodata.json
|
||||||
|
RUN echo '{}' >/tmp/usr/local/etc/xray/99_version.json
|
||||||
|
|
||||||
# Create log files
|
# Create log files
|
||||||
RUN mkdir -p /tmp/var/log/xray && touch \
|
RUN mkdir -p /tmp/var/log/xray && touch \
|
||||||
@@ -54,8 +57,8 @@ RUN mkdir -p /tmp/var/log/xray && touch \
|
|||||||
FROM --platform=linux/amd64 gcr.io/distroless/static:nonroot
|
FROM --platform=linux/amd64 gcr.io/distroless/static:nonroot
|
||||||
|
|
||||||
COPY --from=build --chown=0:0 --chmod=755 /src/xray /usr/local/bin/xray
|
COPY --from=build --chown=0:0 --chmod=755 /src/xray /usr/local/bin/xray
|
||||||
COPY --from=build --chown=0:0 --chmod=755 /tmp/empty /usr/local/share/xray
|
COPY --from=build --chown=65532:65532 --chmod=755 /tmp/empty /usr/local/share/xray
|
||||||
COPY --from=build --chown=0:0 --chmod=644 /tmp/geodat/*.dat /usr/local/share/xray/
|
COPY --from=build --chown=65532:65532 --chmod=644 /tmp/geodat/*.dat /usr/local/share/xray/
|
||||||
COPY --from=build --chown=0:0 --chmod=755 /tmp/empty /usr/local/etc/xray
|
COPY --from=build --chown=0:0 --chmod=755 /tmp/empty /usr/local/etc/xray
|
||||||
COPY --from=build --chown=0:0 --chmod=644 /tmp/usr/local/etc/xray/*.json /usr/local/etc/xray/
|
COPY --from=build --chown=0:0 --chmod=644 /tmp/usr/local/etc/xray/*.json /usr/local/etc/xray/
|
||||||
COPY --from=build --chown=0:0 --chmod=755 /tmp/empty /var/log/xray
|
COPY --from=build --chown=0:0 --chmod=755 /tmp/empty /var/log/xray
|
||||||
|
|||||||
@@ -73,7 +73,6 @@
|
|||||||
- [Xray_bash_onekey](https://github.com/hello-yunshu/Xray_bash_onekey), [XTool](https://github.com/LordPenguin666/XTool), [VPainLess](https://github.com/vpainless/vpainless)
|
- [Xray_bash_onekey](https://github.com/hello-yunshu/Xray_bash_onekey), [XTool](https://github.com/LordPenguin666/XTool), [VPainLess](https://github.com/vpainless/vpainless)
|
||||||
- [v2ray-agent](https://github.com/mack-a/v2ray-agent), [Xray_onekey](https://github.com/wulabing/Xray_onekey), [ProxySU](https://github.com/proxysu/ProxySU)
|
- [v2ray-agent](https://github.com/mack-a/v2ray-agent), [Xray_onekey](https://github.com/wulabing/Xray_onekey), [ProxySU](https://github.com/proxysu/ProxySU)
|
||||||
- Magisk
|
- Magisk
|
||||||
- [NetProxy-Magisk](https://github.com/Fanju6/NetProxy-Magisk)
|
|
||||||
- [Xray4Magisk](https://github.com/Asterisk4Magisk/Xray4Magisk)
|
- [Xray4Magisk](https://github.com/Asterisk4Magisk/Xray4Magisk)
|
||||||
- [Xray_For_Magisk](https://github.com/E7KMbb/Xray_For_Magisk)
|
- [Xray_For_Magisk](https://github.com/E7KMbb/Xray_For_Magisk)
|
||||||
- Homebrew
|
- Homebrew
|
||||||
@@ -111,6 +110,8 @@
|
|||||||
- [Invisible Man - Xray](https://github.com/InvisibleManVPN/InvisibleMan-XRayClient)
|
- [Invisible Man - Xray](https://github.com/InvisibleManVPN/InvisibleMan-XRayClient)
|
||||||
- [AnyPortal](https://github.com/AnyPortal/AnyPortal)
|
- [AnyPortal](https://github.com/AnyPortal/AnyPortal)
|
||||||
- [GenyConnect](https://github.com/genyleap/GenyConnect)
|
- [GenyConnect](https://github.com/genyleap/GenyConnect)
|
||||||
|
- [OneXray](https://github.com/OneXray/OneXray)
|
||||||
|
- [XrayUI-dev](https://github.com/PhoenixNil/XrayUI-dev)
|
||||||
- Android
|
- Android
|
||||||
- [v2rayNG](https://github.com/2dust/v2rayNG)
|
- [v2rayNG](https://github.com/2dust/v2rayNG)
|
||||||
- [X-flutter](https://github.com/XTLS/X-flutter)
|
- [X-flutter](https://github.com/XTLS/X-flutter)
|
||||||
@@ -118,7 +119,7 @@
|
|||||||
- [SimpleXray](https://github.com/lhear/SimpleXray)
|
- [SimpleXray](https://github.com/lhear/SimpleXray)
|
||||||
- [XrayFA](https://github.com/Q7DF1/XrayFA)
|
- [XrayFA](https://github.com/Q7DF1/XrayFA)
|
||||||
- [AnyPortal](https://github.com/AnyPortal/AnyPortal)
|
- [AnyPortal](https://github.com/AnyPortal/AnyPortal)
|
||||||
- [NetProxy-Magisk](https://github.com/Fanju6/NetProxy-Magisk)
|
- [OneXray](https://github.com/OneXray/OneXray)
|
||||||
- iOS & macOS arm64 & tvOS
|
- iOS & macOS arm64 & tvOS
|
||||||
- [Happ](https://apps.apple.com/app/happ-proxy-utility/id6504287215) | [Happ RU](https://apps.apple.com/ru/app/happ-proxy-utility-plus/id6746188973) | [Happ tvOS](https://apps.apple.com/us/app/happ-proxy-utility-for-tv/id6748297274)
|
- [Happ](https://apps.apple.com/app/happ-proxy-utility/id6504287215) | [Happ RU](https://apps.apple.com/ru/app/happ-proxy-utility-plus/id6746188973) | [Happ tvOS](https://apps.apple.com/us/app/happ-proxy-utility-for-tv/id6748297274)
|
||||||
- [Streisand](https://apps.apple.com/app/streisand/id6450534064)
|
- [Streisand](https://apps.apple.com/app/streisand/id6450534064)
|
||||||
@@ -143,10 +144,12 @@
|
|||||||
- [AnyPortal](https://github.com/AnyPortal/AnyPortal)
|
- [AnyPortal](https://github.com/AnyPortal/AnyPortal)
|
||||||
- [v2rayN](https://github.com/2dust/v2rayN)
|
- [v2rayN](https://github.com/2dust/v2rayN)
|
||||||
- [GenyConnect](https://github.com/genyleap/GenyConnect)
|
- [GenyConnect](https://github.com/genyleap/GenyConnect)
|
||||||
|
- [OneXray](https://github.com/OneXray/OneXray)
|
||||||
|
|
||||||
## Others that support VLESS, XTLS, REALITY, XUDP, PLUX...
|
## Others that support VLESS, XTLS, REALITY, XUDP, PLUX...
|
||||||
|
|
||||||
- iOS & macOS arm64 & tvOS
|
- iOS & macOS arm64 & tvOS
|
||||||
|
- [Anywhere](https://github.com/NodePassProject/Anywhere)
|
||||||
- [Shadowrocket](https://apps.apple.com/app/shadowrocket/id932747118)
|
- [Shadowrocket](https://apps.apple.com/app/shadowrocket/id932747118)
|
||||||
- [Loon](https://apps.apple.com/us/app/loon/id1373567447)
|
- [Loon](https://apps.apple.com/us/app/loon/id1373567447)
|
||||||
- [Egern](https://apps.apple.com/us/app/egern/id1616105820)
|
- [Egern](https://apps.apple.com/us/app/egern/id1616105820)
|
||||||
|
|||||||
@@ -4,12 +4,14 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"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/signal/done"
|
"github.com/xtls/xray-core/common/signal/done"
|
||||||
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/transport/internet"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -73,14 +75,27 @@ func (c *Commander) Start() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if len(c.listen) > 0 {
|
if len(c.listen) > 0 {
|
||||||
if l, err := net.Listen("tcp", c.listen); err != nil {
|
var addr net.Addr
|
||||||
|
|
||||||
|
if strings.HasPrefix(c.listen, "/") || strings.HasPrefix(c.listen, "@") {
|
||||||
|
addr = &net.UnixAddr{Name: c.listen, Net: "unix"}
|
||||||
|
} else {
|
||||||
|
tcpAddr, err := net.ResolveTCPAddr("tcp", c.listen)
|
||||||
|
if err != nil {
|
||||||
|
errors.LogErrorInner(context.Background(), err, "API server failed to parse listen address ", c.listen)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
addr = tcpAddr
|
||||||
|
}
|
||||||
|
l, err := internet.ListenSystem(context.Background(), addr, nil)
|
||||||
|
if err != nil {
|
||||||
errors.LogErrorInner(context.Background(), err, "API server failed to listen on ", c.listen)
|
errors.LogErrorInner(context.Background(), err, "API server failed to listen on ", c.listen)
|
||||||
return err
|
return err
|
||||||
} else {
|
|
||||||
errors.LogInfo(context.Background(), "API server listening on ", l.Addr())
|
|
||||||
go listen(l)
|
|
||||||
}
|
}
|
||||||
|
errors.LogInfo(context.Background(), "API server listening on ", l.Addr())
|
||||||
|
go listen(l)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+29
-90
@@ -5,18 +5,16 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
go_errors "errors"
|
go_errors "errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
"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/geodata"
|
"github.com/xtls/xray-core/common/geodata"
|
||||||
"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/common/utils"
|
||||||
"github.com/xtls/xray-core/features/dns"
|
"github.com/xtls/xray-core/features/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -158,9 +156,12 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
|
|||||||
clients = append(clients, client)
|
clients = append(clients, client)
|
||||||
}
|
}
|
||||||
|
|
||||||
domainMatcher, err := geodata.DomainReg.BuildDomainMatcher(effectiveRules)
|
var domainMatcher geodata.DomainMatcher
|
||||||
if err != nil {
|
if len(effectiveRules) > 0 {
|
||||||
return nil, err
|
domainMatcher, err = geodata.DomainReg.BuildDomainMatcher(effectiveRules)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there is no DNS client in config, add a `localhost` DNS client
|
// If there is no DNS client in config, add a `localhost` DNS client
|
||||||
@@ -220,7 +221,7 @@ func (s *DNS) LookupIP(domain string, option dns.IPOption) ([]net.IP, uint32, er
|
|||||||
}
|
}
|
||||||
|
|
||||||
if s.checkSystem {
|
if s.checkSystem {
|
||||||
supportIPv4, supportIPv6 := checkRoutes()
|
supportIPv4, supportIPv6 := utils.CheckRoutes()
|
||||||
option.IPv4Enable = option.IPv4Enable && supportIPv4
|
option.IPv4Enable = option.IPv4Enable && supportIPv4
|
||||||
option.IPv6Enable = option.IPv6Enable && supportIPv6
|
option.IPv6Enable = option.IPv6Enable && supportIPv6
|
||||||
} else {
|
} else {
|
||||||
@@ -271,25 +272,27 @@ func (s *DNS) sortClients(domain string) []*Client {
|
|||||||
|
|
||||||
// Priority domain matching
|
// Priority domain matching
|
||||||
hasMatch := false
|
hasMatch := false
|
||||||
matchSlice := s.domainMatcher.Match(strings.ToLower(domain))
|
if s.domainMatcher != nil {
|
||||||
sort.Slice(matchSlice, func(i, j int) bool {
|
matchSlice := s.domainMatcher.Match(strings.ToLower(domain))
|
||||||
return matchSlice[i] < matchSlice[j]
|
sort.Slice(matchSlice, func(i, j int) bool {
|
||||||
})
|
return matchSlice[i] < matchSlice[j]
|
||||||
for _, match := range matchSlice {
|
})
|
||||||
info := s.matcherInfos[match]
|
for _, match := range matchSlice {
|
||||||
client := s.clients[info.clientIdx]
|
info := s.matcherInfos[match]
|
||||||
domainRule := info.domainRule
|
client := s.clients[info.clientIdx]
|
||||||
domainRules = append(domainRules, fmt.Sprintf("%s(DNS idx:%d)", domainRule, info.clientIdx))
|
domainRule := info.domainRule
|
||||||
if clientUsed[info.clientIdx] {
|
domainRules = append(domainRules, fmt.Sprintf("%s(DNS idx:%d)", domainRule, info.clientIdx))
|
||||||
continue
|
if clientUsed[info.clientIdx] {
|
||||||
}
|
continue
|
||||||
clientUsed[info.clientIdx] = true
|
}
|
||||||
clients = append(clients, client)
|
clientUsed[info.clientIdx] = true
|
||||||
clientNames = append(clientNames, client.Name())
|
clients = append(clients, client)
|
||||||
hasMatch = true
|
clientNames = append(clientNames, client.Name())
|
||||||
if client.finalQuery {
|
hasMatch = true
|
||||||
logDecision(s.ctx, domain, domainRules, clientNames)
|
if client.finalQuery {
|
||||||
return clients
|
logDecision(s.ctx, domain, domainRules, clientNames)
|
||||||
|
return clients
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -534,67 +537,3 @@ func init() {
|
|||||||
return New(ctx, config.(*Config))
|
return New(ctx, config.(*Config))
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func probeRoutes() (ipv4 bool, ipv6 bool) {
|
|
||||||
if conn, err := net.Dial("udp4", "192.33.4.12:53"); err == nil {
|
|
||||||
ipv4 = true
|
|
||||||
conn.Close()
|
|
||||||
}
|
|
||||||
if conn, err := net.Dial("udp6", "[2001:500:2::c]:53"); err == nil {
|
|
||||||
ipv6 = true
|
|
||||||
conn.Close()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var routeCache struct {
|
|
||||||
sync.Once
|
|
||||||
sync.RWMutex
|
|
||||||
expire time.Time
|
|
||||||
ipv4, ipv6 bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkRoutes() (bool, bool) {
|
|
||||||
if !isGUIPlatform {
|
|
||||||
routeCache.Once.Do(func() {
|
|
||||||
routeCache.ipv4, routeCache.ipv6 = probeRoutes()
|
|
||||||
})
|
|
||||||
return routeCache.ipv4, routeCache.ipv6
|
|
||||||
}
|
|
||||||
|
|
||||||
routeCache.RWMutex.RLock()
|
|
||||||
now := time.Now()
|
|
||||||
if routeCache.expire.After(now) {
|
|
||||||
routeCache.RWMutex.RUnlock()
|
|
||||||
return routeCache.ipv4, routeCache.ipv6
|
|
||||||
}
|
|
||||||
routeCache.RWMutex.RUnlock()
|
|
||||||
|
|
||||||
routeCache.RWMutex.Lock()
|
|
||||||
defer routeCache.RWMutex.Unlock()
|
|
||||||
|
|
||||||
now = time.Now()
|
|
||||||
if routeCache.expire.After(now) { // double-check
|
|
||||||
return routeCache.ipv4, routeCache.ipv6
|
|
||||||
}
|
|
||||||
routeCache.ipv4, routeCache.ipv6 = probeRoutes() // ~2ms
|
|
||||||
routeCache.expire = now.Add(100 * time.Millisecond) // ttl
|
|
||||||
return routeCache.ipv4, routeCache.ipv6
|
|
||||||
}
|
|
||||||
|
|
||||||
var isGUIPlatform = detectGUIPlatform()
|
|
||||||
|
|
||||||
func detectGUIPlatform() bool {
|
|
||||||
switch runtime.GOOS {
|
|
||||||
case "android", "ios", "windows", "darwin":
|
|
||||||
return true
|
|
||||||
case "linux", "freebsd", "openbsd":
|
|
||||||
if t := os.Getenv("XDG_SESSION_TYPE"); t == "wayland" || t == "x11" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if os.Getenv("DISPLAY") != "" || os.Getenv("WAYLAND_DISPLAY") != "" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|||||||
+8
-8
@@ -148,7 +148,7 @@ func TestUDPServerSubnet(t *testing.T) {
|
|||||||
Outbound: []*core.OutboundHandlerConfig{
|
Outbound: []*core.OutboundHandlerConfig{
|
||||||
{
|
{
|
||||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||||
IpsBlocked: &freedom.IPRules{},
|
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -210,7 +210,7 @@ func TestUDPServer(t *testing.T) {
|
|||||||
Outbound: []*core.OutboundHandlerConfig{
|
Outbound: []*core.OutboundHandlerConfig{
|
||||||
{
|
{
|
||||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||||
IpsBlocked: &freedom.IPRules{},
|
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -350,7 +350,7 @@ func TestPrioritizedDomain(t *testing.T) {
|
|||||||
Outbound: []*core.OutboundHandlerConfig{
|
Outbound: []*core.OutboundHandlerConfig{
|
||||||
{
|
{
|
||||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||||
IpsBlocked: &freedom.IPRules{},
|
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -421,7 +421,7 @@ func TestUDPServerIPv6(t *testing.T) {
|
|||||||
Outbound: []*core.OutboundHandlerConfig{
|
Outbound: []*core.OutboundHandlerConfig{
|
||||||
{
|
{
|
||||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||||
IpsBlocked: &freedom.IPRules{},
|
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -490,7 +490,7 @@ func TestStaticHostDomain(t *testing.T) {
|
|||||||
Outbound: []*core.OutboundHandlerConfig{
|
Outbound: []*core.OutboundHandlerConfig{
|
||||||
{
|
{
|
||||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||||
IpsBlocked: &freedom.IPRules{},
|
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -577,7 +577,7 @@ func TestIPMatch(t *testing.T) {
|
|||||||
Outbound: []*core.OutboundHandlerConfig{
|
Outbound: []*core.OutboundHandlerConfig{
|
||||||
{
|
{
|
||||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||||
IpsBlocked: &freedom.IPRules{},
|
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -696,7 +696,7 @@ func TestLocalDomain(t *testing.T) {
|
|||||||
Outbound: []*core.OutboundHandlerConfig{
|
Outbound: []*core.OutboundHandlerConfig{
|
||||||
{
|
{
|
||||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||||
IpsBlocked: &freedom.IPRules{},
|
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -953,7 +953,7 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
|
|||||||
Outbound: []*core.OutboundHandlerConfig{
|
Outbound: []*core.OutboundHandlerConfig{
|
||||||
{
|
{
|
||||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||||
IpsBlocked: &freedom.IPRules{},
|
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
+8
-12
@@ -17,7 +17,7 @@ import (
|
|||||||
type Holder struct {
|
type Holder struct {
|
||||||
domainToIP cache.Lru
|
domainToIP cache.Lru
|
||||||
ipRange *net.IPNet
|
ipRange *net.IPNet
|
||||||
mu *sync.Mutex
|
mu sync.Mutex
|
||||||
|
|
||||||
config *FakeDnsPool
|
config *FakeDnsPool
|
||||||
}
|
}
|
||||||
@@ -49,9 +49,7 @@ func (fkdns *Holder) Start() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (fkdns *Holder) Close() error {
|
func (fkdns *Holder) Close() error {
|
||||||
fkdns.domainToIP = nil
|
// nothing to do for now, just wait GC
|
||||||
fkdns.ipRange = nil
|
|
||||||
fkdns.mu = nil
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +68,7 @@ func NewFakeDNSHolder() (*Holder, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewFakeDNSHolderConfigOnly(conf *FakeDnsPool) (*Holder, error) {
|
func NewFakeDNSHolderConfigOnly(conf *FakeDnsPool) (*Holder, error) {
|
||||||
return &Holder{nil, nil, nil, conf}, nil
|
return &Holder{config: conf}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fkdns *Holder) initializeFromConfig() error {
|
func (fkdns *Holder) initializeFromConfig() error {
|
||||||
@@ -92,7 +90,6 @@ func (fkdns *Holder) initialize(ipPoolCidr string, lruSize int) error {
|
|||||||
}
|
}
|
||||||
fkdns.domainToIP = cache.NewLru(lruSize)
|
fkdns.domainToIP = cache.NewLru(lruSize)
|
||||||
fkdns.ipRange = ipRange
|
fkdns.ipRange = ipRange
|
||||||
fkdns.mu = new(sync.Mutex)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,7 +100,7 @@ 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)}
|
||||||
}
|
}
|
||||||
currentTimeMillis := uint64(time.Now().UnixNano() / 1e6)
|
currentTimeMillis := uint64(time.Now().UnixMilli())
|
||||||
ones, bits := fkdns.ipRange.Mask.Size()
|
ones, bits := fkdns.ipRange.Mask.Size()
|
||||||
rooms := bits - ones
|
rooms := bits - ones
|
||||||
if rooms < 64 {
|
if rooms < 64 {
|
||||||
@@ -202,12 +199,11 @@ func (h *HolderMulti) Start() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *HolderMulti) Close() error {
|
func (h *HolderMulti) Close() error {
|
||||||
|
var errs []error
|
||||||
for _, v := range h.holders {
|
for _, v := range h.holders {
|
||||||
if err := v.Close(); err != nil {
|
errs = append(errs, v.Close())
|
||||||
return errors.New("Cannot close all fake dns pools").Base(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
return errors.Combine(errs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HolderMulti) createHolderGroups() error {
|
func (h *HolderMulti) createHolderGroups() error {
|
||||||
@@ -222,7 +218,7 @@ func (h *HolderMulti) createHolderGroups() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewFakeDNSHolderMulti(conf *FakeDnsPoolMulti) (*HolderMulti, error) {
|
func NewFakeDNSHolderMulti(conf *FakeDnsPoolMulti) (*HolderMulti, error) {
|
||||||
holderMulti := &HolderMulti{nil, conf}
|
holderMulti := &HolderMulti{config: conf}
|
||||||
if err := holderMulti.createHolderGroups(); err != nil {
|
if err := holderMulti.createHolderGroups(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-11
@@ -13,8 +13,8 @@ import (
|
|||||||
|
|
||||||
// StaticHosts represents static domain-ip mapping in DNS server.
|
// StaticHosts represents static domain-ip mapping in DNS server.
|
||||||
type StaticHosts struct {
|
type StaticHosts struct {
|
||||||
reps [][]net.Address
|
responses [][]net.Address
|
||||||
matcher geodata.DomainMatcher
|
matcher geodata.DomainMatcher
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewStaticHosts creates a new StaticHosts instance.
|
// NewStaticHosts creates a new StaticHosts instance.
|
||||||
@@ -45,21 +45,21 @@ func NewStaticHosts(hosts []*Config_HostMapping) (*StaticHosts, error) {
|
|||||||
rep = append(rep, addr)
|
rep = append(rep, addr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// if len(rep) == 0 {
|
|
||||||
// errors.LogError(context.Background(), "empty value in static hosts, ignore this rule: ", mapping.Domain)
|
|
||||||
// continue
|
|
||||||
// }
|
|
||||||
reps = append(reps, rep)
|
reps = append(reps, rep)
|
||||||
rules = append(rules, mapping.Domain)
|
rules = append(rules, mapping.Domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(rules) == 0 {
|
||||||
|
return &StaticHosts{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
matcher, err := geodata.DomainReg.BuildDomainMatcher(rules)
|
matcher, err := geodata.DomainReg.BuildDomainMatcher(rules)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &StaticHosts{
|
return &StaticHosts{
|
||||||
reps: reps,
|
responses: reps,
|
||||||
matcher: matcher,
|
matcher: matcher,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,8 +76,8 @@ func filterIP(ips []net.Address, option dns.IPOption) []net.Address {
|
|||||||
func (h *StaticHosts) lookupInternal(domain string) ([]net.Address, error) {
|
func (h *StaticHosts) lookupInternal(domain string) ([]net.Address, error) {
|
||||||
ips := make([]net.Address, 0)
|
ips := make([]net.Address, 0)
|
||||||
found := false
|
found := false
|
||||||
for _, ruleIdx := range h.matcher.Match(domain) {
|
for _, idx := range h.matcher.Match(domain) {
|
||||||
for _, rep := range h.reps[ruleIdx] {
|
for _, rep := range h.responses[idx] {
|
||||||
if err, ok := rep.(dns.RCodeError); ok {
|
if err, ok := rep.(dns.RCodeError); ok {
|
||||||
if uint16(err) == 0 {
|
if uint16(err) == 0 {
|
||||||
return nil, dns.ErrEmptyResponse
|
return nil, dns.ErrEmptyResponse
|
||||||
@@ -85,7 +85,7 @@ func (h *StaticHosts) lookupInternal(domain string) ([]net.Address, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ips = append(ips, h.reps[ruleIdx]...)
|
ips = append(ips, h.responses[idx]...)
|
||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
if !found {
|
if !found {
|
||||||
@@ -122,5 +122,8 @@ func (h *StaticHosts) lookup(domain string, option dns.IPOption, maxDepth int) (
|
|||||||
|
|
||||||
// Lookup returns IP addresses or proxied domain for the given domain, if exists in this StaticHosts.
|
// 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, error) {
|
func (h *StaticHosts) Lookup(domain string, option dns.IPOption) ([]net.Address, error) {
|
||||||
|
if h.matcher == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
return h.lookup(domain, option, 5)
|
return h.lookup(domain, option, 5)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/xtls/xray-core/common/geodata"
|
"github.com/xtls/xray-core/common/geodata"
|
||||||
"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/common/utils"
|
||||||
"github.com/xtls/xray-core/core"
|
"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/routing"
|
"github.com/xtls/xray-core/features/routing"
|
||||||
@@ -166,7 +167,7 @@ func (c *Client) Name() string {
|
|||||||
// QueryIP sends DNS query to the name server with the client's IP.
|
// QueryIP sends DNS query to the name server with the client's IP.
|
||||||
func (c *Client) QueryIP(ctx context.Context, domain string, option dns.IPOption) ([]net.IP, uint32, error) {
|
func (c *Client) QueryIP(ctx context.Context, domain string, option dns.IPOption) ([]net.IP, uint32, error) {
|
||||||
if c.checkSystem {
|
if c.checkSystem {
|
||||||
supportIPv4, supportIPv6 := checkRoutes()
|
supportIPv4, supportIPv6 := utils.CheckRoutes()
|
||||||
option.IPv4Enable = option.IPv4Enable && supportIPv4
|
option.IPv4Enable = option.IPv4Enable && supportIPv4
|
||||||
option.IPv6Enable = option.IPv6Enable && supportIPv6
|
option.IPv6Enable = option.IPv6Enable && supportIPv6
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -0,0 +1,198 @@
|
|||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-go v1.36.11
|
||||||
|
// protoc v6.33.5
|
||||||
|
// source: app/geodata/config.proto
|
||||||
|
|
||||||
|
package geodata
|
||||||
|
|
||||||
|
import (
|
||||||
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
reflect "reflect"
|
||||||
|
sync "sync"
|
||||||
|
unsafe "unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Verify that this generated code is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||||
|
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
|
)
|
||||||
|
|
||||||
|
type Asset struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"`
|
||||||
|
File string `protobuf:"bytes,2,opt,name=file,proto3" json:"file,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Asset) Reset() {
|
||||||
|
*x = Asset{}
|
||||||
|
mi := &file_app_geodata_config_proto_msgTypes[0]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Asset) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Asset) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Asset) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_app_geodata_config_proto_msgTypes[0]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Asset.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Asset) Descriptor() ([]byte, []int) {
|
||||||
|
return file_app_geodata_config_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Asset) GetUrl() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Url
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Asset) GetFile() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.File
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Cron string `protobuf:"bytes,1,opt,name=cron,proto3" json:"cron,omitempty"`
|
||||||
|
Outbound string `protobuf:"bytes,2,opt,name=outbound,proto3" json:"outbound,omitempty"`
|
||||||
|
Assets []*Asset `protobuf:"bytes,3,rep,name=assets,proto3" json:"assets,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) Reset() {
|
||||||
|
*x = Config{}
|
||||||
|
mi := &file_app_geodata_config_proto_msgTypes[1]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Config) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Config) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_app_geodata_config_proto_msgTypes[1]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Config) Descriptor() ([]byte, []int) {
|
||||||
|
return file_app_geodata_config_proto_rawDescGZIP(), []int{1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetCron() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Cron
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetOutbound() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Outbound
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetAssets() []*Asset {
|
||||||
|
if x != nil {
|
||||||
|
return x.Assets
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var File_app_geodata_config_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
|
const file_app_geodata_config_proto_rawDesc = "" +
|
||||||
|
"\n" +
|
||||||
|
"\x18app/geodata/config.proto\x12\x10xray.app.geodata\"-\n" +
|
||||||
|
"\x05Asset\x12\x10\n" +
|
||||||
|
"\x03url\x18\x01 \x01(\tR\x03url\x12\x12\n" +
|
||||||
|
"\x04file\x18\x02 \x01(\tR\x04file\"i\n" +
|
||||||
|
"\x06Config\x12\x12\n" +
|
||||||
|
"\x04cron\x18\x01 \x01(\tR\x04cron\x12\x1a\n" +
|
||||||
|
"\boutbound\x18\x02 \x01(\tR\boutbound\x12/\n" +
|
||||||
|
"\x06assets\x18\x03 \x03(\v2\x17.xray.app.geodata.AssetR\x06assetsBR\n" +
|
||||||
|
"\x14com.xray.app.geodataP\x01Z%github.com/xtls/xray-core/app/geodata\xaa\x02\x10Xray.App.Geodatab\x06proto3"
|
||||||
|
|
||||||
|
var (
|
||||||
|
file_app_geodata_config_proto_rawDescOnce sync.Once
|
||||||
|
file_app_geodata_config_proto_rawDescData []byte
|
||||||
|
)
|
||||||
|
|
||||||
|
func file_app_geodata_config_proto_rawDescGZIP() []byte {
|
||||||
|
file_app_geodata_config_proto_rawDescOnce.Do(func() {
|
||||||
|
file_app_geodata_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_app_geodata_config_proto_rawDesc), len(file_app_geodata_config_proto_rawDesc)))
|
||||||
|
})
|
||||||
|
return file_app_geodata_config_proto_rawDescData
|
||||||
|
}
|
||||||
|
|
||||||
|
var file_app_geodata_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||||
|
var file_app_geodata_config_proto_goTypes = []any{
|
||||||
|
(*Asset)(nil), // 0: xray.app.geodata.Asset
|
||||||
|
(*Config)(nil), // 1: xray.app.geodata.Config
|
||||||
|
}
|
||||||
|
var file_app_geodata_config_proto_depIdxs = []int32{
|
||||||
|
0, // 0: xray.app.geodata.Config.assets:type_name -> xray.app.geodata.Asset
|
||||||
|
1, // [1:1] is the sub-list for method output_type
|
||||||
|
1, // [1:1] is the sub-list for method input_type
|
||||||
|
1, // [1:1] is the sub-list for extension type_name
|
||||||
|
1, // [1:1] is the sub-list for extension extendee
|
||||||
|
0, // [0:1] is the sub-list for field type_name
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { file_app_geodata_config_proto_init() }
|
||||||
|
func file_app_geodata_config_proto_init() {
|
||||||
|
if File_app_geodata_config_proto != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
type x struct{}
|
||||||
|
out := protoimpl.TypeBuilder{
|
||||||
|
File: protoimpl.DescBuilder{
|
||||||
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_app_geodata_config_proto_rawDesc), len(file_app_geodata_config_proto_rawDesc)),
|
||||||
|
NumEnums: 0,
|
||||||
|
NumMessages: 2,
|
||||||
|
NumExtensions: 0,
|
||||||
|
NumServices: 0,
|
||||||
|
},
|
||||||
|
GoTypes: file_app_geodata_config_proto_goTypes,
|
||||||
|
DependencyIndexes: file_app_geodata_config_proto_depIdxs,
|
||||||
|
MessageInfos: file_app_geodata_config_proto_msgTypes,
|
||||||
|
}.Build()
|
||||||
|
File_app_geodata_config_proto = out.File
|
||||||
|
file_app_geodata_config_proto_goTypes = nil
|
||||||
|
file_app_geodata_config_proto_depIdxs = nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package xray.app.geodata;
|
||||||
|
option csharp_namespace = "Xray.App.Geodata";
|
||||||
|
option go_package = "github.com/xtls/xray-core/app/geodata";
|
||||||
|
option java_package = "com.xray.app.geodata";
|
||||||
|
option java_multiple_files = true;
|
||||||
|
|
||||||
|
message Asset {
|
||||||
|
string url = 1;
|
||||||
|
|
||||||
|
string file = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Config {
|
||||||
|
string cron = 1;
|
||||||
|
|
||||||
|
string outbound = 2;
|
||||||
|
|
||||||
|
repeated Asset assets = 3;
|
||||||
|
}
|
||||||
@@ -0,0 +1,304 @@
|
|||||||
|
package geodata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
go_errors "errors"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/common/platform/filesystem"
|
||||||
|
"github.com/xtls/xray-core/common/task"
|
||||||
|
"github.com/xtls/xray-core/common/utils"
|
||||||
|
"github.com/xtls/xray-core/features/routing"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/tagged"
|
||||||
|
)
|
||||||
|
|
||||||
|
const idleTimeout = 30 * time.Second
|
||||||
|
|
||||||
|
type stage struct {
|
||||||
|
target string
|
||||||
|
temp string
|
||||||
|
}
|
||||||
|
|
||||||
|
type downloader struct {
|
||||||
|
ctx context.Context
|
||||||
|
client *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
type idleConn struct {
|
||||||
|
net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *idleConn) Read(b []byte) (int, error) {
|
||||||
|
t := time.AfterFunc(idleTimeout, func() {
|
||||||
|
_ = c.Close()
|
||||||
|
})
|
||||||
|
|
||||||
|
n, err := c.Conn.Read(b)
|
||||||
|
if !t.Stop() {
|
||||||
|
_ = c.Close()
|
||||||
|
return n, errors.New("connection idle timeout")
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *idleConn) Write(b []byte) (int, error) {
|
||||||
|
return c.Conn.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDownloader(ctx context.Context, dispatcher routing.Dispatcher, outbound string) *downloader {
|
||||||
|
return &downloader{
|
||||||
|
ctx: ctx,
|
||||||
|
client: newClient(ctx, dispatcher, outbound),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newClient(baseCtx context.Context, dispatcher routing.Dispatcher, outbound string) *http.Client {
|
||||||
|
return &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
Proxy: nil,
|
||||||
|
DisableKeepAlives: true,
|
||||||
|
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||||
|
var conn net.Conn
|
||||||
|
err := task.Run(ctx, func() error {
|
||||||
|
if tagged.Dialer == nil {
|
||||||
|
return errors.New("tagged dialer is not initialized")
|
||||||
|
}
|
||||||
|
dest, err := net.ParseDestination(network + ":" + address)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("cannot understand address").Base(err)
|
||||||
|
}
|
||||||
|
c, err := tagged.Dialer(baseCtx, dispatcher, dest, outbound)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("cannot dial remote address ", dest).Base(err)
|
||||||
|
}
|
||||||
|
conn = c
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("cannot finish connection").Base(err)
|
||||||
|
}
|
||||||
|
return &idleConn{
|
||||||
|
Conn: conn,
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
TLSHandshakeTimeout: idleTimeout,
|
||||||
|
ResponseHeaderTimeout: idleTimeout,
|
||||||
|
},
|
||||||
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||||
|
if req.URL.Scheme != "https" {
|
||||||
|
return errors.New("redirected to non-https URL: ", req.URL.String())
|
||||||
|
}
|
||||||
|
if len(via) >= 10 {
|
||||||
|
return errors.New("stopped after 10 redirects")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *downloader) download(assets []*Asset) ([]stage, error) {
|
||||||
|
staged := make([]stage, 0, len(assets))
|
||||||
|
for _, asset := range assets {
|
||||||
|
stage, err := d.downloadOne(asset)
|
||||||
|
if err != nil {
|
||||||
|
clean(staged)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
staged = append(staged, stage)
|
||||||
|
}
|
||||||
|
return staged, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *downloader) downloadOne(asset *Asset) (stage, error) {
|
||||||
|
target, err := filesystem.ResolveAsset(asset.File)
|
||||||
|
if err != nil {
|
||||||
|
return stage{}, err
|
||||||
|
}
|
||||||
|
errors.LogInfo(d.ctx, "downloading geodata asset from ", asset.Url, " to ", target)
|
||||||
|
|
||||||
|
temp, err := tempFile(target, ".tmp")
|
||||||
|
if err != nil {
|
||||||
|
return stage{}, err
|
||||||
|
}
|
||||||
|
tempName := temp.Name()
|
||||||
|
keepTemp := false
|
||||||
|
defer func() {
|
||||||
|
if !keepTemp {
|
||||||
|
os.Remove(tempName)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := d.fetch(asset.Url, temp); err != nil {
|
||||||
|
temp.Close()
|
||||||
|
return stage{}, err
|
||||||
|
}
|
||||||
|
if err := temp.Chmod(0o644); err != nil {
|
||||||
|
temp.Close()
|
||||||
|
return stage{}, err
|
||||||
|
}
|
||||||
|
if err := temp.Close(); err != nil {
|
||||||
|
return stage{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
keepTemp = true
|
||||||
|
return stage{
|
||||||
|
target: target,
|
||||||
|
temp: tempName,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *downloader) fetch(rawURL string, writer io.Writer) error {
|
||||||
|
req, err := http.NewRequestWithContext(d.ctx, http.MethodGet, rawURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
utils.TryDefaultHeadersWith(req.Header, "nav")
|
||||||
|
|
||||||
|
resp, err := d.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
|
||||||
|
io.Copy(io.Discard, resp.Body)
|
||||||
|
return errors.New("unexpected status code: ", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := io.Copy(writer, resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
return errors.New("empty response body")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func clean(assets []stage) {
|
||||||
|
for _, asset := range assets {
|
||||||
|
if asset.temp != "" {
|
||||||
|
os.Remove(asset.temp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type tx struct {
|
||||||
|
swaps []swap
|
||||||
|
}
|
||||||
|
|
||||||
|
type swap struct {
|
||||||
|
target string
|
||||||
|
backup string
|
||||||
|
hadOriginal bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func swapAll(assets []stage) (*tx, error) {
|
||||||
|
t := &tx{}
|
||||||
|
for _, asset := range assets {
|
||||||
|
s, err := swapOne(asset)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Combine(err, t.rollback())
|
||||||
|
}
|
||||||
|
t.swaps = append(t.swaps, s)
|
||||||
|
}
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func swapOne(asset stage) (swap, error) {
|
||||||
|
backup, err := backupFile(asset.target)
|
||||||
|
if err != nil {
|
||||||
|
return swap{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s := swap{
|
||||||
|
target: asset.target,
|
||||||
|
backup: backup,
|
||||||
|
}
|
||||||
|
if err := os.Rename(asset.target, backup); err != nil {
|
||||||
|
if !go_errors.Is(err, os.ErrNotExist) {
|
||||||
|
return swap{}, err
|
||||||
|
}
|
||||||
|
if err := os.Remove(backup); err != nil && !go_errors.Is(err, os.ErrNotExist) {
|
||||||
|
return swap{}, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s.hadOriginal = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Rename(asset.temp, asset.target); err != nil {
|
||||||
|
if s.hadOriginal {
|
||||||
|
if restoreErr := os.Rename(backup, asset.target); restoreErr != nil {
|
||||||
|
return swap{}, errors.Combine(err, restoreErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return swap{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tx) rollback() error {
|
||||||
|
var errs []error
|
||||||
|
for i := len(t.swaps) - 1; i >= 0; i-- {
|
||||||
|
if err := t.swaps[i].rollback(); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors.Combine(errs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s swap) rollback() error {
|
||||||
|
var errs []error
|
||||||
|
if err := os.Remove(s.target); err != nil && !go_errors.Is(err, os.ErrNotExist) {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
if s.hadOriginal {
|
||||||
|
if err := os.Rename(s.backup, s.target); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
} else if err := os.Remove(s.backup); err != nil && !go_errors.Is(err, os.ErrNotExist) {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
return errors.Combine(errs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tx) commit() error {
|
||||||
|
var errs []error
|
||||||
|
for _, swap := range t.swaps {
|
||||||
|
if err := os.Remove(swap.backup); err != nil && !go_errors.Is(err, os.ErrNotExist) {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors.Combine(errs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tempFile(target string, suffix string) (*os.File, error) {
|
||||||
|
dir := filepath.Dir(target)
|
||||||
|
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return os.CreateTemp(dir, "."+filepath.Base(target)+".*"+suffix)
|
||||||
|
}
|
||||||
|
|
||||||
|
func backupFile(target string) (string, error) {
|
||||||
|
file, err := tempFile(target, ".bak")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
name := file.Name()
|
||||||
|
if err := file.Close(); err != nil {
|
||||||
|
os.Remove(name)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if err := os.Remove(name); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return name, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
package geodata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/robfig/cron/v3"
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
commongeodata "github.com/xtls/xray-core/common/geodata"
|
||||||
|
"github.com/xtls/xray-core/core"
|
||||||
|
"github.com/xtls/xray-core/features/routing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Instance struct {
|
||||||
|
assets []*Asset
|
||||||
|
downloader *downloader
|
||||||
|
tasker *cron.Cron
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
running bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(ctx context.Context, config *Config) (*Instance, error) {
|
||||||
|
if config.Cron == "" {
|
||||||
|
return &Instance{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
g := &Instance{
|
||||||
|
assets: config.Assets,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(g.assets) > 0 {
|
||||||
|
var dispatcher routing.Dispatcher
|
||||||
|
if err := core.RequireFeatures(ctx, func(d routing.Dispatcher) {
|
||||||
|
dispatcher = d
|
||||||
|
}); err != nil {
|
||||||
|
return nil, errors.New("failed to get dispatcher for geodata downloader").Base(err)
|
||||||
|
}
|
||||||
|
g.downloader = newDownloader(ctx, dispatcher, config.Outbound)
|
||||||
|
}
|
||||||
|
|
||||||
|
g.tasker = cron.New(
|
||||||
|
cron.WithChain(cron.SkipIfStillRunning(cron.DiscardLogger)),
|
||||||
|
cron.WithLogger(cron.DiscardLogger),
|
||||||
|
)
|
||||||
|
if _, err := g.tasker.AddFunc(config.Cron, g.execute); err != nil {
|
||||||
|
return nil, errors.New("invalid geodata cron").Base(err)
|
||||||
|
}
|
||||||
|
errors.LogInfo(ctx, "scheduled geodata reload with cron: ", config.Cron)
|
||||||
|
|
||||||
|
return g, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Instance) execute() {
|
||||||
|
var err error
|
||||||
|
if g.downloader != nil {
|
||||||
|
err = g.reloadWithUpdate()
|
||||||
|
} else {
|
||||||
|
err = reload()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
errors.LogErrorInner(context.Background(), err, "scheduled geodata reload failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Instance) reloadWithUpdate() error {
|
||||||
|
staged, err := g.downloader.download(g.assets)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer clean(staged)
|
||||||
|
|
||||||
|
tx, err := swapAll(staged)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := reload(); err != nil {
|
||||||
|
errors.LogErrorInner(context.Background(), err, "failed to reload geodata after downloading assets, rolling back")
|
||||||
|
rollbackErr := tx.rollback()
|
||||||
|
return errors.Combine(err, rollbackErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx.commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
func reload() error {
|
||||||
|
return errors.Combine(commongeodata.IPReg.Reload(), commongeodata.DomainReg.Reload())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Instance) Type() interface{} {
|
||||||
|
return (*Instance)(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Instance) Start() error {
|
||||||
|
g.mu.Lock()
|
||||||
|
defer g.mu.Unlock()
|
||||||
|
|
||||||
|
if g.running {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if g.tasker != nil {
|
||||||
|
g.tasker.Start()
|
||||||
|
}
|
||||||
|
|
||||||
|
g.running = true
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Instance) Close() error {
|
||||||
|
g.mu.Lock()
|
||||||
|
defer g.mu.Unlock()
|
||||||
|
|
||||||
|
if !g.running {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if g.tasker != nil {
|
||||||
|
<-g.tasker.Stop().Done()
|
||||||
|
}
|
||||||
|
|
||||||
|
g.running = false
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, cfg interface{}) (interface{}, error) {
|
||||||
|
return New(ctx, cfg.(*Config))
|
||||||
|
}))
|
||||||
|
}
|
||||||
@@ -18,9 +18,9 @@ import (
|
|||||||
"github.com/xtls/xray-core/features/routing"
|
"github.com/xtls/xray-core/features/routing"
|
||||||
"github.com/xtls/xray-core/features/stats"
|
"github.com/xtls/xray-core/features/stats"
|
||||||
"github.com/xtls/xray-core/proxy"
|
"github.com/xtls/xray-core/proxy"
|
||||||
"github.com/xtls/xray-core/proxy/hysteria/account"
|
hysteria_proxy "github.com/xtls/xray-core/proxy/hysteria"
|
||||||
hyCtx "github.com/xtls/xray-core/proxy/hysteria/ctx"
|
|
||||||
"github.com/xtls/xray-core/transport/internet"
|
"github.com/xtls/xray-core/transport/internet"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/hysteria"
|
||||||
"github.com/xtls/xray-core/transport/internet/stat"
|
"github.com/xtls/xray-core/transport/internet/stat"
|
||||||
"github.com/xtls/xray-core/transport/internet/tcp"
|
"github.com/xtls/xray-core/transport/internet/tcp"
|
||||||
"github.com/xtls/xray-core/transport/internet/udp"
|
"github.com/xtls/xray-core/transport/internet/udp"
|
||||||
@@ -134,10 +134,8 @@ func (w *tcpWorker) Proxy() proxy.Inbound {
|
|||||||
func (w *tcpWorker) Start() error {
|
func (w *tcpWorker) Start() error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
type HysteriaInboundValidator interface{ HysteriaInboundValidator() *account.Validator }
|
if v, ok := w.proxy.(*hysteria_proxy.Server); ok {
|
||||||
if v, ok := w.proxy.(HysteriaInboundValidator); ok {
|
ctx = hysteria.ContextWithValidator(ctx, v.HysteriaInboundValidator())
|
||||||
ctx = hyCtx.ContextWithRequireDatagram(ctx, true)
|
|
||||||
ctx = hyCtx.ContextWithValidator(ctx, v.HysteriaInboundValidator())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hub, err := internet.ListenTCP(ctx, w.address, w.port, w.stream, func(conn stat.Connection) {
|
hub, err := internet.ListenTCP(ctx, w.address, w.port, w.stream, func(conn stat.Connection) {
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ func TestOutboundWithoutStatCounter(t *testing.T) {
|
|||||||
ctx = session.ContextWithOutbounds(ctx, []*session.Outbound{{}})
|
ctx = session.ContextWithOutbounds(ctx, []*session.Outbound{{}})
|
||||||
h, _ := NewHandler(ctx, &core.OutboundHandlerConfig{
|
h, _ := NewHandler(ctx, &core.OutboundHandlerConfig{
|
||||||
Tag: "tag",
|
Tag: "tag",
|
||||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
|
ProxySettings: serial.ToTypedMessage(&freedom.Config{FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}}}),
|
||||||
})
|
})
|
||||||
conn, _ := h.(*Handler).Dial(ctx, net.TCPDestination(net.DomainAddress("localhost"), 13146))
|
conn, _ := h.(*Handler).Dial(ctx, net.TCPDestination(net.DomainAddress("localhost"), 13146))
|
||||||
_, ok := conn.(*stat.CounterConnection)
|
_, ok := conn.(*stat.CounterConnection)
|
||||||
@@ -78,7 +78,7 @@ func TestOutboundWithStatCounter(t *testing.T) {
|
|||||||
ctx = session.ContextWithOutbounds(ctx, []*session.Outbound{{}})
|
ctx = session.ContextWithOutbounds(ctx, []*session.Outbound{{}})
|
||||||
h, _ := NewHandler(ctx, &core.OutboundHandlerConfig{
|
h, _ := NewHandler(ctx, &core.OutboundHandlerConfig{
|
||||||
Tag: "tag",
|
Tag: "tag",
|
||||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
|
ProxySettings: serial.ToTypedMessage(&freedom.Config{FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}}}),
|
||||||
})
|
})
|
||||||
conn, _ := h.(*Handler).Dial(ctx, net.TCPDestination(net.DomainAddress("localhost"), 13146))
|
conn, _ := h.(*Handler).Dial(ctx, net.TCPDestination(net.DomainAddress("localhost"), 13146))
|
||||||
_, ok := conn.(*stat.CounterConnection)
|
_, ok := conn.(*stat.CounterConnection)
|
||||||
@@ -118,7 +118,7 @@ func TestTagsCache(t *testing.T) {
|
|||||||
tag := fmt.Sprintf("%s%d", tags_prefix, idx)
|
tag := fmt.Sprintf("%s%d", tags_prefix, idx)
|
||||||
cfg := &core.OutboundHandlerConfig{
|
cfg := &core.OutboundHandlerConfig{
|
||||||
Tag: tag,
|
Tag: tag,
|
||||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
|
ProxySettings: serial.ToTypedMessage(&freedom.Config{FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}}}),
|
||||||
}
|
}
|
||||||
if h, err := NewHandler(ctx, cfg); err == nil {
|
if h, err := NewHandler(ctx, cfg); err == nil {
|
||||||
if err := ohm.AddHandler(ctx, h); err == nil {
|
if err := ohm.AddHandler(ctx, h); err == nil {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/xtls/xray-core/common/geodata"
|
"github.com/xtls/xray-core/common/geodata"
|
||||||
"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/features/routing/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Condition interface {
|
type Condition interface {
|
||||||
@@ -356,7 +357,13 @@ func (m *ProcessNameMatcher) Apply(ctx routing.Context) bool {
|
|||||||
|
|
||||||
var dstIP string
|
var dstIP string
|
||||||
var dstPort uint16 = 0
|
var dstPort uint16 = 0
|
||||||
if len(ctx.GetTargetIPs()) > 0 {
|
|
||||||
|
// do not use resolved IP because Android process lookup needs original dst ip
|
||||||
|
resolvableContext, ok := ctx.(*dns.ResolvableContext)
|
||||||
|
if ok && len(resolvableContext.Context.GetTargetIPs()) > 0 {
|
||||||
|
dstIP = resolvableContext.Context.GetTargetIPs()[0].String()
|
||||||
|
dstPort = uint16(resolvableContext.Context.GetTargetPort())
|
||||||
|
} else if len(ctx.GetTargetIPs()) > 0 {
|
||||||
dstIP = ctx.GetTargetIPs()[0].String()
|
dstIP = ctx.GetTargetIPs()[0].String()
|
||||||
dstPort = uint16(ctx.GetTargetPort())
|
dstPort = uint16(ctx.GetTargetPort())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,10 +23,54 @@ type DomainMatcherFactory interface {
|
|||||||
BuildMatcher(rules []*DomainRule) (DomainMatcher, error)
|
BuildMatcher(rules []*DomainRule) (DomainMatcher, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type MphDomainMatcherFactory struct{}
|
type MphDomainMatcherFactory struct {
|
||||||
|
sync.Mutex
|
||||||
|
shared map[string]strmatcher.MatcherGroup // TODO: cleanup
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildDomainRulesKey(rules []*DomainRule) string {
|
||||||
|
var sb strings.Builder
|
||||||
|
cache := false
|
||||||
|
for _, r := range rules {
|
||||||
|
switch v := r.Value.(type) {
|
||||||
|
case *DomainRule_Custom:
|
||||||
|
sb.WriteString(v.Custom.Type.String())
|
||||||
|
sb.WriteString(":")
|
||||||
|
sb.WriteString(v.Custom.Value)
|
||||||
|
sb.WriteString(",")
|
||||||
|
case *DomainRule_Geosite:
|
||||||
|
cache = true
|
||||||
|
sb.WriteString(v.Geosite.File)
|
||||||
|
sb.WriteString(":")
|
||||||
|
sb.WriteString(v.Geosite.Code)
|
||||||
|
sb.WriteString("@")
|
||||||
|
sb.WriteString(v.Geosite.Attrs)
|
||||||
|
sb.WriteString(",")
|
||||||
|
default:
|
||||||
|
panic("unknown domain rule type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !cache {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
// BuildMatcher implements DomainMatcherFactory.
|
// BuildMatcher implements DomainMatcherFactory.
|
||||||
func (f *MphDomainMatcherFactory) BuildMatcher(rules []*DomainRule) (DomainMatcher, error) {
|
func (f *MphDomainMatcherFactory) BuildMatcher(rules []*DomainRule) (DomainMatcher, error) {
|
||||||
|
if len(rules) == 0 {
|
||||||
|
return nil, errors.New("empty domain rule list")
|
||||||
|
}
|
||||||
|
key := buildDomainRulesKey(rules)
|
||||||
|
if key != "" {
|
||||||
|
f.Lock()
|
||||||
|
defer f.Unlock()
|
||||||
|
if g := f.shared[key]; g != nil {
|
||||||
|
errors.LogDebug(context.Background(), "geodata mph domain matcher cache HIT for ", len(rules), " rules")
|
||||||
|
return g, nil
|
||||||
|
}
|
||||||
|
errors.LogDebug(context.Background(), "geodata mph domain matcher cache MISS for ", len(rules), " rules")
|
||||||
|
}
|
||||||
g := strmatcher.NewMphValueMatcher()
|
g := strmatcher.NewMphValueMatcher()
|
||||||
for i, r := range rules {
|
for i, r := range rules {
|
||||||
switch v := r.Value.(type) {
|
switch v := r.Value.(type) {
|
||||||
@@ -57,25 +101,30 @@ func (f *MphDomainMatcherFactory) BuildMatcher(rules []*DomainRule) (DomainMatch
|
|||||||
if err := g.Build(); err != nil {
|
if err := g.Build(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if key != "" {
|
||||||
|
f.shared[key] = g
|
||||||
|
}
|
||||||
return g, nil
|
return g, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type CompactDomainMatcherFactory struct {
|
type CompactDomainMatcherFactory struct {
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
shared map[string]strmatcher.MatcherGroup // TODO: cleanup
|
shared map[string]strmatcher.MatcherSet // TODO: cleanup
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *CompactDomainMatcherFactory) getOrCreateFrom(rule *GeoSiteRule) (strmatcher.MatcherGroup, error) {
|
func (f *CompactDomainMatcherFactory) getOrCreateFrom(rule *GeoSiteRule) (strmatcher.MatcherSet, error) {
|
||||||
key := rule.File + ":" + rule.Code + "@" + rule.Attrs
|
key := rule.File + ":" + rule.Code + "@" + rule.Attrs
|
||||||
|
|
||||||
f.Lock()
|
f.Lock()
|
||||||
defer f.Unlock()
|
defer f.Unlock()
|
||||||
|
|
||||||
if m := f.shared[key]; m != nil {
|
if s := f.shared[key]; s != nil {
|
||||||
return m, nil
|
errors.LogDebug(context.Background(), "geodata geosite matcher cache HIT ", key)
|
||||||
|
return s, nil
|
||||||
}
|
}
|
||||||
|
errors.LogDebug(context.Background(), "geodata geosite matcher cache MISS ", key)
|
||||||
|
|
||||||
g := strmatcher.NewLinearValueMatcher()
|
s := strmatcher.NewLinearAnyMatcher()
|
||||||
domains, err := loadSiteWithAttrs(rule.File, rule.Code, rule.Attrs)
|
domains, err := loadSiteWithAttrs(rule.File, rule.Code, rule.Attrs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -87,16 +136,19 @@ func (f *CompactDomainMatcherFactory) getOrCreateFrom(rule *GeoSiteRule) (strmat
|
|||||||
errors.LogError(context.Background(), "ignore invalid geosite entry in ", rule.File, ":", rule.Code, " at index ", i, ", ", err)
|
errors.LogError(context.Background(), "ignore invalid geosite entry in ", rule.File, ":", rule.Code, " at index ", i, ", ", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
g.Add(m, 0)
|
s.Add(m)
|
||||||
}
|
}
|
||||||
f.shared[key] = g
|
f.shared[key] = s
|
||||||
return g, err
|
return s, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuildMatcher implements DomainMatcherFactory.
|
// BuildMatcher implements DomainMatcherFactory.
|
||||||
func (f *CompactDomainMatcherFactory) BuildMatcher(rules []*DomainRule) (DomainMatcher, error) {
|
func (f *CompactDomainMatcherFactory) BuildMatcher(rules []*DomainRule) (DomainMatcher, error) {
|
||||||
|
if len(rules) == 0 {
|
||||||
|
return nil, errors.New("empty domain rule list")
|
||||||
|
}
|
||||||
compact := &CompactDomainMatcher{
|
compact := &CompactDomainMatcher{
|
||||||
matchers: make([]strmatcher.MatcherGroup, 0, len(rules)),
|
matchers: make([]strmatcher.MatcherSet, 0, len(rules)),
|
||||||
values: make([]uint32, 0, len(rules)),
|
values: make([]uint32, 0, len(rules)),
|
||||||
}
|
}
|
||||||
for i, r := range rules {
|
for i, r := range rules {
|
||||||
@@ -126,7 +178,7 @@ func (f *CompactDomainMatcherFactory) BuildMatcher(rules []*DomainRule) (DomainM
|
|||||||
|
|
||||||
type CompactDomainMatcher struct {
|
type CompactDomainMatcher struct {
|
||||||
custom strmatcher.ValueMatcher
|
custom strmatcher.ValueMatcher
|
||||||
matchers []strmatcher.MatcherGroup
|
matchers []strmatcher.MatcherSet
|
||||||
values []uint32
|
values []uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,8 +230,8 @@ func parseDomain(d *Domain) (strmatcher.Matcher, error) {
|
|||||||
func newDomainMatcherFactory() DomainMatcherFactory {
|
func newDomainMatcherFactory() DomainMatcherFactory {
|
||||||
switch runtime.GOOS {
|
switch runtime.GOOS {
|
||||||
case "ios", "android":
|
case "ios", "android":
|
||||||
return &CompactDomainMatcherFactory{shared: make(map[string]strmatcher.MatcherGroup)}
|
return &CompactDomainMatcherFactory{shared: make(map[string]strmatcher.MatcherSet)}
|
||||||
default:
|
default:
|
||||||
return &MphDomainMatcherFactory{}
|
return &MphDomainMatcherFactory{shared: make(map[string]strmatcher.MatcherGroup)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestCompactDomainMatcher_PreservesCustomRuleIndices(t *testing.T) {
|
func TestCompactDomainMatcher_PreservesCustomRuleIndices(t *testing.T) {
|
||||||
factory := &CompactDomainMatcherFactory{shared: make(map[string]strmatcher.MatcherGroup)}
|
factory := &CompactDomainMatcherFactory{shared: make(map[string]strmatcher.MatcherSet)}
|
||||||
matcher, err := factory.BuildMatcher([]*DomainRule{
|
matcher, err := factory.BuildMatcher([]*DomainRule{
|
||||||
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Full, Value: "example.com"}}},
|
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Full, Value: "example.com"}}},
|
||||||
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Domain, Value: "example.com"}}},
|
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Domain, Value: "example.com"}}},
|
||||||
@@ -31,7 +31,7 @@ func TestCompactDomainMatcher_PreservesCustomRuleIndices(t *testing.T) {
|
|||||||
func TestCompactDomainMatcher_PreservesMixedRuleIndices(t *testing.T) {
|
func TestCompactDomainMatcher_PreservesMixedRuleIndices(t *testing.T) {
|
||||||
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
||||||
|
|
||||||
factory := &CompactDomainMatcherFactory{shared: make(map[string]strmatcher.MatcherGroup)}
|
factory := &CompactDomainMatcherFactory{shared: make(map[string]strmatcher.MatcherSet)}
|
||||||
matcher, err := factory.BuildMatcher([]*DomainRule{
|
matcher, err := factory.BuildMatcher([]*DomainRule{
|
||||||
{Value: &DomainRule_Geosite{Geosite: &GeoSiteRule{File: DefaultGeoSiteDat, Code: "CN"}}},
|
{Value: &DomainRule_Geosite{Geosite: &GeoSiteRule{File: DefaultGeoSiteDat, Code: "CN"}}},
|
||||||
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Full, Value: "163.com"}}},
|
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Full, Value: "163.com"}}},
|
||||||
@@ -50,7 +50,7 @@ func TestCompactDomainMatcher_PreservesMixedRuleIndices(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMphDomainMatcher_MatchReturnsDetachedSlice(t *testing.T) {
|
func TestMphDomainMatcher_MatchReturnsDetachedSlice(t *testing.T) {
|
||||||
matcher, err := (&MphDomainMatcherFactory{}).BuildMatcher([]*DomainRule{
|
matcher, err := (&MphDomainMatcherFactory{shared: make(map[string]strmatcher.MatcherGroup)}).BuildMatcher([]*DomainRule{
|
||||||
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Full, Value: "example.com"}}},
|
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Full, Value: "example.com"}}},
|
||||||
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Domain, Value: "example.com"}}},
|
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Domain, Value: "example.com"}}},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,11 +1,59 @@
|
|||||||
package geodata
|
package geodata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
)
|
||||||
|
|
||||||
type DomainRegistry struct {
|
type DomainRegistry struct {
|
||||||
factory DomainMatcherFactory
|
mu sync.Mutex
|
||||||
|
factory DomainMatcherFactory
|
||||||
|
matchers []*DynamicDomainMatcher
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *DomainRegistry) BuildDomainMatcher(rules []*DomainRule) (DomainMatcher, error) {
|
func (r *DomainRegistry) BuildDomainMatcher(rules []*DomainRule) (DomainMatcher, error) {
|
||||||
return r.factory.BuildMatcher(rules)
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
|
||||||
|
m, err := r.factory.BuildMatcher(rules)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
d := NewDynamicDomainMatcher(rules, m)
|
||||||
|
r.matchers = append(r.matchers, d)
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DomainRegistry) Reload() error {
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
|
||||||
|
errors.LogInfo(context.Background(), "reloading GeoSite data for ", len(r.matchers), " domain matcher(s)")
|
||||||
|
|
||||||
|
factory := newDomainMatcherFactory()
|
||||||
|
type reloadEntry struct {
|
||||||
|
dynamic *DynamicDomainMatcher
|
||||||
|
matcher DomainMatcher
|
||||||
|
}
|
||||||
|
reloaded := make([]reloadEntry, len(r.matchers))
|
||||||
|
for i, d := range r.matchers {
|
||||||
|
m, err := factory.BuildMatcher(d.rules)
|
||||||
|
if err != nil {
|
||||||
|
errors.LogErrorInner(context.Background(), err, "failed to reload GeoSite data for domain matcher ", i)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
reloaded[i] = reloadEntry{dynamic: d, matcher: m}
|
||||||
|
}
|
||||||
|
for _, entry := range reloaded {
|
||||||
|
entry.dynamic.Reload(entry.matcher)
|
||||||
|
}
|
||||||
|
r.factory = factory
|
||||||
|
errors.LogInfo(context.Background(), "reloaded GeoSite data for ", len(r.matchers), " domain matcher(s)")
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDomainRegistry() *DomainRegistry {
|
func newDomainRegistry() *DomainRegistry {
|
||||||
@@ -15,3 +63,32 @@ func newDomainRegistry() *DomainRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var DomainReg = newDomainRegistry()
|
var DomainReg = newDomainRegistry()
|
||||||
|
|
||||||
|
type domainMatcherState struct {
|
||||||
|
matcher DomainMatcher
|
||||||
|
}
|
||||||
|
|
||||||
|
type DynamicDomainMatcher struct {
|
||||||
|
rules []*DomainRule
|
||||||
|
state atomic.Pointer[domainMatcherState]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match implements DomainMatcher.
|
||||||
|
func (d *DynamicDomainMatcher) Match(input string) []uint32 {
|
||||||
|
return d.state.Load().matcher.Match(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchAny implements DomainMatcher.
|
||||||
|
func (d *DynamicDomainMatcher) MatchAny(input string) bool {
|
||||||
|
return d.state.Load().matcher.MatchAny(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DynamicDomainMatcher) Reload(newMatcher DomainMatcher) {
|
||||||
|
d.state.Store(&domainMatcherState{matcher: newMatcher})
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDynamicDomainMatcher(rules []*DomainRule, matcher DomainMatcher) *DynamicDomainMatcher {
|
||||||
|
d := &DynamicDomainMatcher{rules: rules}
|
||||||
|
d.Reload(matcher)
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|||||||
@@ -816,8 +816,10 @@ func (f *IPSetFactory) GetOrCreateFromGeoIPRules(rules []*GeoIPRule) (*IPSet, er
|
|||||||
defer f.Unlock()
|
defer f.Unlock()
|
||||||
|
|
||||||
if ipset := f.shared[key]; ipset != nil {
|
if ipset := f.shared[key]; ipset != nil {
|
||||||
|
errors.LogDebug(context.Background(), "geodata geoip matcher cache HIT ", key)
|
||||||
return ipset, nil
|
return ipset, nil
|
||||||
}
|
}
|
||||||
|
errors.LogDebug(context.Background(), "geodata geoip matcher cache MISS ", key)
|
||||||
|
|
||||||
ipset, err := f.createFrom(func(add func(*CIDR)) error {
|
ipset, err := f.createFrom(func(add func(*CIDR)) error {
|
||||||
for _, r := range rules {
|
for _, r := range rules {
|
||||||
@@ -915,24 +917,31 @@ func (f *IPSetFactory) createFrom(yield func(func(*CIDR)) error) (*IPSet, error)
|
|||||||
return nil, errors.New("failed to build IPv6 set").Base(err)
|
return nil, errors.New("failed to build IPv6 set").Base(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var has4, has6 bool
|
||||||
var max4, max6 int
|
var max4, max6 int
|
||||||
|
|
||||||
for _, p := range ipv4.Prefixes() {
|
for _, p := range ipv4.Prefixes() {
|
||||||
|
has4 = true
|
||||||
if b := p.Bits(); b > max4 {
|
if b := p.Bits(); b > max4 {
|
||||||
max4 = b
|
max4 = b
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, p := range ipv6.Prefixes() {
|
for _, p := range ipv6.Prefixes() {
|
||||||
|
has6 = true
|
||||||
if b := p.Bits(); b > max6 {
|
if b := p.Bits(); b > max6 {
|
||||||
max6 = b
|
max6 = b
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if max4 == 0 {
|
if !has4 {
|
||||||
max4 = 0xff
|
max4 = 0xff
|
||||||
|
} else if max4 == 0 {
|
||||||
|
max4 = 0xfe
|
||||||
}
|
}
|
||||||
if max6 == 0 {
|
if !has6 {
|
||||||
max6 = 0xff
|
max6 = 0xff
|
||||||
|
} else if max6 == 0 {
|
||||||
|
max6 = 0xfe
|
||||||
}
|
}
|
||||||
|
|
||||||
return &IPSet{ipv4: ipv4, ipv6: ipv6, max4: uint8(max4), max6: uint8(max6)}, nil
|
return &IPSet{ipv4: ipv4, ipv6: ipv6, max4: uint8(max4), max6: uint8(max6)}, nil
|
||||||
@@ -1007,3 +1016,7 @@ func buildOptimizedIPMatcher(f *IPSetFactory, rules []*IPRule) (IPMatcher, error
|
|||||||
return &HeuristicMultiIPMatcher{matchers: subs}, nil
|
return &HeuristicMultiIPMatcher{matchers: subs}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newIPSetFactory() *IPSetFactory {
|
||||||
|
return &IPSetFactory{shared: make(map[string]*IPSet)}
|
||||||
|
}
|
||||||
|
|||||||
@@ -97,6 +97,90 @@ func TestIPMatcher(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIPMatcherFullCIDR4(t *testing.T) {
|
||||||
|
matcher := buildIPMatcher(
|
||||||
|
"0.0.0.0/0",
|
||||||
|
)
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
Input string
|
||||||
|
Output bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Input: "192.168.1.1",
|
||||||
|
Output: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Input: "0.0.0.0",
|
||||||
|
Output: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Input: "255.255.255.255",
|
||||||
|
Output: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Input: "2001:cdba::3257:9652",
|
||||||
|
Output: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Input: "::0",
|
||||||
|
Output: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Input: "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
|
||||||
|
Output: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
if v := matcher.Match(xnet.ParseAddress(test.Input).IP()); v != test.Output {
|
||||||
|
t.Error("unexpected output: ", v, " for test case ", test)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIPMatcherFullCIDR6(t *testing.T) {
|
||||||
|
matcher := buildIPMatcher(
|
||||||
|
"::0/0",
|
||||||
|
)
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
Input string
|
||||||
|
Output bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Input: "192.168.1.1",
|
||||||
|
Output: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Input: "0.0.0.0",
|
||||||
|
Output: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Input: "255.255.255.255",
|
||||||
|
Output: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Input: "2001:cdba::3257:9652",
|
||||||
|
Output: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Input: "::0",
|
||||||
|
Output: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Input: "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
|
||||||
|
Output: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
if v := matcher.Match(xnet.ParseAddress(test.Input).IP()); v != test.Output {
|
||||||
|
t.Error("unexpected output: ", v, " for test case ", test)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestIPMatcherRegression(t *testing.T) {
|
func TestIPMatcherRegression(t *testing.T) {
|
||||||
matcher := buildIPMatcher(
|
matcher := buildIPMatcher(
|
||||||
"98.108.20.0/22",
|
"98.108.20.0/22",
|
||||||
|
|||||||
@@ -1,17 +1,135 @@
|
|||||||
package geodata
|
package geodata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
)
|
||||||
|
|
||||||
type IPRegistry struct {
|
type IPRegistry struct {
|
||||||
|
mu sync.Mutex
|
||||||
ipsetFactory *IPSetFactory
|
ipsetFactory *IPSetFactory
|
||||||
|
matchers []*DynamicIPMatcher
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *IPRegistry) BuildIPMatcher(rules []*IPRule) (IPMatcher, error) {
|
func (r *IPRegistry) BuildIPMatcher(rules []*IPRule) (IPMatcher, error) {
|
||||||
return buildOptimizedIPMatcher(r.ipsetFactory, rules)
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
|
||||||
|
m, err := buildOptimizedIPMatcher(r.ipsetFactory, rules)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
d := NewDynamicIPMatcher(rules, m)
|
||||||
|
r.matchers = append(r.matchers, d)
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *IPRegistry) Reload() error {
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
|
||||||
|
errors.LogInfo(context.Background(), "reloading GeoIP data for ", len(r.matchers), " IP matcher(s)")
|
||||||
|
|
||||||
|
factory := newIPSetFactory()
|
||||||
|
type reloadEntry struct {
|
||||||
|
dynamic *DynamicIPMatcher
|
||||||
|
matcher IPMatcher
|
||||||
|
}
|
||||||
|
reloaded := make([]reloadEntry, len(r.matchers))
|
||||||
|
for i, d := range r.matchers {
|
||||||
|
m, err := buildOptimizedIPMatcher(factory, d.rules)
|
||||||
|
if err != nil {
|
||||||
|
errors.LogErrorInner(context.Background(), err, "failed to reload GeoIP data for IP matcher ", i)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
reloaded[i] = reloadEntry{dynamic: d, matcher: m}
|
||||||
|
}
|
||||||
|
for _, entry := range reloaded {
|
||||||
|
entry.dynamic.Reload(entry.matcher)
|
||||||
|
}
|
||||||
|
r.ipsetFactory = factory
|
||||||
|
errors.LogInfo(context.Background(), "reloaded GeoIP data for ", len(r.matchers), " IP matcher(s)")
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newIPRegistry() *IPRegistry {
|
func newIPRegistry() *IPRegistry {
|
||||||
return &IPRegistry{
|
return &IPRegistry{
|
||||||
ipsetFactory: &IPSetFactory{shared: make(map[string]*IPSet)},
|
ipsetFactory: newIPSetFactory(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var IPReg = newIPRegistry()
|
var IPReg = newIPRegistry()
|
||||||
|
|
||||||
|
type ipMatcherState struct {
|
||||||
|
matcher IPMatcher
|
||||||
|
}
|
||||||
|
|
||||||
|
type DynamicIPMatcher struct {
|
||||||
|
rules []*IPRule
|
||||||
|
state atomic.Pointer[ipMatcherState]
|
||||||
|
mu sync.Mutex
|
||||||
|
reverse bool
|
||||||
|
reverseSet bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match implements IPMatcher.
|
||||||
|
func (d *DynamicIPMatcher) Match(ip net.IP) bool {
|
||||||
|
return d.state.Load().matcher.Match(ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnyMatch implements IPMatcher.
|
||||||
|
func (d *DynamicIPMatcher) AnyMatch(ips []net.IP) bool {
|
||||||
|
return d.state.Load().matcher.AnyMatch(ips)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matches implements IPMatcher.
|
||||||
|
func (d *DynamicIPMatcher) Matches(ips []net.IP) bool {
|
||||||
|
return d.state.Load().matcher.Matches(ips)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FilterIPs implements IPMatcher.
|
||||||
|
func (d *DynamicIPMatcher) FilterIPs(ips []net.IP) (matched []net.IP, unmatched []net.IP) {
|
||||||
|
return d.state.Load().matcher.FilterIPs(ips)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToggleReverse implements IPMatcher.
|
||||||
|
func (d *DynamicIPMatcher) ToggleReverse() {
|
||||||
|
d.mu.Lock()
|
||||||
|
defer d.mu.Unlock()
|
||||||
|
|
||||||
|
d.reverse = !d.reverse
|
||||||
|
d.state.Load().matcher.ToggleReverse()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetReverse implements IPMatcher.
|
||||||
|
func (d *DynamicIPMatcher) SetReverse(reverse bool) {
|
||||||
|
d.mu.Lock()
|
||||||
|
defer d.mu.Unlock()
|
||||||
|
|
||||||
|
d.reverse = reverse
|
||||||
|
d.reverseSet = true
|
||||||
|
d.state.Load().matcher.SetReverse(reverse)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DynamicIPMatcher) Reload(newMatcher IPMatcher) {
|
||||||
|
d.mu.Lock()
|
||||||
|
defer d.mu.Unlock()
|
||||||
|
|
||||||
|
if d.reverseSet {
|
||||||
|
newMatcher.SetReverse(d.reverse)
|
||||||
|
} else if d.reverse {
|
||||||
|
newMatcher.ToggleReverse()
|
||||||
|
}
|
||||||
|
d.state.Store(&ipMatcherState{matcher: newMatcher})
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDynamicIPMatcher(rules []*IPRule, matcher IPMatcher) *DynamicIPMatcher {
|
||||||
|
d := &DynamicIPMatcher{rules: rules}
|
||||||
|
d.Reload(matcher)
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package strmatcher
|
||||||
|
|
||||||
|
// LinearAnyMatcher is an implementation of AnyMatcher.
|
||||||
|
type LinearAnyMatcher struct {
|
||||||
|
full *FullMatcherSet
|
||||||
|
domain *DomainMatcherSet
|
||||||
|
substr *SubstrMatcherSet
|
||||||
|
regex *SimpleMatcherSet
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLinearAnyMatcher() *LinearAnyMatcher {
|
||||||
|
return new(LinearAnyMatcher)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add implements AnyMatcher.Add.
|
||||||
|
func (s *LinearAnyMatcher) Add(matcher Matcher) {
|
||||||
|
switch matcher := matcher.(type) {
|
||||||
|
case FullMatcher:
|
||||||
|
if s.full == nil {
|
||||||
|
s.full = NewFullMatcherSet()
|
||||||
|
}
|
||||||
|
s.full.AddFullMatcher(matcher)
|
||||||
|
case DomainMatcher:
|
||||||
|
if s.domain == nil {
|
||||||
|
s.domain = NewDomainMatcherSet()
|
||||||
|
}
|
||||||
|
s.domain.AddDomainMatcher(matcher)
|
||||||
|
case SubstrMatcher:
|
||||||
|
if s.substr == nil {
|
||||||
|
s.substr = new(SubstrMatcherSet)
|
||||||
|
}
|
||||||
|
s.substr.AddSubstrMatcher(matcher)
|
||||||
|
default:
|
||||||
|
if s.regex == nil {
|
||||||
|
s.regex = new(SimpleMatcherSet)
|
||||||
|
}
|
||||||
|
s.regex.AddMatcher(matcher)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchAny implements AnyMatcher.MatchAny.
|
||||||
|
func (s *LinearAnyMatcher) MatchAny(input string) bool {
|
||||||
|
if s.full != nil && s.full.MatchAny(input) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if s.domain != nil && s.domain.MatchAny(input) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if s.substr != nil && s.substr.MatchAny(input) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return s.regex != nil && s.regex.MatchAny(input)
|
||||||
|
}
|
||||||
@@ -100,10 +100,6 @@ func (t Type) New(pattern string) (Matcher, error) {
|
|||||||
case Substr:
|
case Substr:
|
||||||
return SubstrMatcher(pattern), nil
|
return SubstrMatcher(pattern), nil
|
||||||
case Domain:
|
case Domain:
|
||||||
pattern, err := ToDomain(pattern)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return DomainMatcher(pattern), nil
|
return DomainMatcher(pattern), nil
|
||||||
case Regex: // 1. regex matching is case-sensitive
|
case Regex: // 1. regex matching is case-sensitive
|
||||||
regex, err := regexp.Compile(pattern)
|
regex, err := regexp.Compile(pattern)
|
||||||
@@ -288,3 +284,65 @@ func CompositeMatchesReverse(matches [][]uint32) []uint32 {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MatcherSetForAll is an interface indicating a MatcherSet could accept all types of matchers.
|
||||||
|
type MatcherSetForAll interface {
|
||||||
|
AddMatcher(matcher Matcher)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatcherSetForFull is an interface indicating a MatcherSet could accept FullMatchers.
|
||||||
|
type MatcherSetForFull interface {
|
||||||
|
AddFullMatcher(matcher FullMatcher)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatcherSetForDomain is an interface indicating a MatcherSet could accept DomainMatchers.
|
||||||
|
type MatcherSetForDomain interface {
|
||||||
|
AddDomainMatcher(matcher DomainMatcher)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatcherSetForSubstr is an interface indicating a MatcherSet could accept SubstrMatchers.
|
||||||
|
type MatcherSetForSubstr interface {
|
||||||
|
AddSubstrMatcher(matcher SubstrMatcher)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatcherSetForRegex is an interface indicating a MatcherSet could accept RegexMatchers.
|
||||||
|
type MatcherSetForRegex interface {
|
||||||
|
AddRegexMatcher(matcher *RegexMatcher)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddMatcherToSet is a helper function to try to add a Matcher to any kind of MatcherSet.
|
||||||
|
// It returns error if the MatcherSet does not accept the provided Matcher's type.
|
||||||
|
// This function is provided to help writing code to test a MatcherSet.
|
||||||
|
func AddMatcherToSet(s MatcherSet, matcher Matcher) error {
|
||||||
|
if s, ok := s.(IndexMatcher); ok {
|
||||||
|
s.Add(matcher)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if s, ok := s.(MatcherSetForAll); ok {
|
||||||
|
s.AddMatcher(matcher)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
switch matcher := matcher.(type) {
|
||||||
|
case FullMatcher:
|
||||||
|
if s, ok := s.(MatcherSetForFull); ok {
|
||||||
|
s.AddFullMatcher(matcher)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
case DomainMatcher:
|
||||||
|
if s, ok := s.(MatcherSetForDomain); ok {
|
||||||
|
s.AddDomainMatcher(matcher)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
case SubstrMatcher:
|
||||||
|
if s, ok := s.(MatcherSetForSubstr); ok {
|
||||||
|
s.AddSubstrMatcher(matcher)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
case *RegexMatcher:
|
||||||
|
if s, ok := s.(MatcherSetForRegex); ok {
|
||||||
|
s.AddRegexMatcher(matcher)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors.New("cannot add matcher to matcher set")
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package strmatcher
|
||||||
|
|
||||||
|
type trieNode2 struct {
|
||||||
|
matched bool
|
||||||
|
children map[string]*trieNode2
|
||||||
|
}
|
||||||
|
|
||||||
|
// DomainMatcherSet is an implementation of MatcherSet.
|
||||||
|
// It uses trie to optimize both memory consumption and lookup speed. Trie node is domain label based.
|
||||||
|
type DomainMatcherSet struct {
|
||||||
|
root *trieNode2
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDomainMatcherSet() *DomainMatcherSet {
|
||||||
|
return &DomainMatcherSet{
|
||||||
|
root: new(trieNode2),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddDomainMatcher implements MatcherSetForDomain.AddDomainMatcher.
|
||||||
|
func (s *DomainMatcherSet) AddDomainMatcher(matcher DomainMatcher) {
|
||||||
|
node := s.root
|
||||||
|
pattern := matcher.Pattern()
|
||||||
|
for i := len(pattern); i > 0; {
|
||||||
|
var part string
|
||||||
|
for j := i - 1; ; j-- {
|
||||||
|
if pattern[j] == '.' {
|
||||||
|
part = pattern[j+1 : i]
|
||||||
|
i = j
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if j == 0 {
|
||||||
|
part = pattern[j:i]
|
||||||
|
i = j
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if node.children == nil {
|
||||||
|
node.children = make(map[string]*trieNode2)
|
||||||
|
}
|
||||||
|
next := node.children[part]
|
||||||
|
if next == nil {
|
||||||
|
next = new(trieNode2)
|
||||||
|
node.children[part] = next
|
||||||
|
}
|
||||||
|
node = next
|
||||||
|
}
|
||||||
|
|
||||||
|
node.matched = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchAny implements MatcherSet.MatchAny.
|
||||||
|
func (s *DomainMatcherSet) MatchAny(input string) bool {
|
||||||
|
node := s.root
|
||||||
|
for i := len(input); i > 0; {
|
||||||
|
for j := i - 1; ; j-- {
|
||||||
|
if input[j] == '.' {
|
||||||
|
node = node.children[input[j+1:i]]
|
||||||
|
i = j
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if j == 0 {
|
||||||
|
node = node.children[input[j:i]]
|
||||||
|
i = j
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if node == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if node.matched {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if node.children == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package strmatcher_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "github.com/xtls/xray-core/common/geodata/strmatcher"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDomainMatcherSet(t *testing.T) {
|
||||||
|
patterns := []struct {
|
||||||
|
Pattern string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Pattern: "example.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "google.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "x.a.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "a.b.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "c.a.b.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "x.y.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "x.y.com",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
testCases := []struct {
|
||||||
|
Domain string
|
||||||
|
Result bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Domain: "x.example.com",
|
||||||
|
Result: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Domain: "y.com",
|
||||||
|
Result: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Domain: "a.b.com",
|
||||||
|
Result: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Domain: "c.a.b.com",
|
||||||
|
Result: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Domain: "c.a..b.com",
|
||||||
|
Result: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Domain: ".com",
|
||||||
|
Result: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Domain: "com",
|
||||||
|
Result: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Domain: "",
|
||||||
|
Result: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Domain: "x.y.com",
|
||||||
|
Result: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
s := NewDomainMatcherSet()
|
||||||
|
for _, pattern := range patterns {
|
||||||
|
AddMatcherToSet(s, DomainMatcher(pattern.Pattern))
|
||||||
|
}
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
r := s.MatchAny(testCase.Domain)
|
||||||
|
if !reflect.DeepEqual(r, testCase.Result) {
|
||||||
|
t.Error("Failed to match domain: ", testCase.Domain, ", expect ", testCase.Result, ", but got ", r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEmptyDomainMatcherSet(t *testing.T) {
|
||||||
|
s := NewDomainMatcherSet()
|
||||||
|
r := s.MatchAny("example.com")
|
||||||
|
if r {
|
||||||
|
t.Error("Expect false, but ", r)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package strmatcher
|
||||||
|
|
||||||
|
// FullMatcherSet is an implementation of MatcherSet.
|
||||||
|
// It uses a hash table to facilitate exact match lookup.
|
||||||
|
type FullMatcherSet struct {
|
||||||
|
matchers map[string]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFullMatcherSet() *FullMatcherSet {
|
||||||
|
return &FullMatcherSet{
|
||||||
|
matchers: make(map[string]struct{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddFullMatcher implements MatcherSetForFull.AddFullMatcher.
|
||||||
|
func (s *FullMatcherSet) AddFullMatcher(matcher FullMatcher) {
|
||||||
|
s.matchers[matcher.Pattern()] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchAny implements MatcherSet.Any.
|
||||||
|
func (s *FullMatcherSet) MatchAny(input string) bool {
|
||||||
|
_, found := s.matchers[input]
|
||||||
|
return found
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package strmatcher_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "github.com/xtls/xray-core/common/geodata/strmatcher"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFullMatcherSet(t *testing.T) {
|
||||||
|
patterns := []struct {
|
||||||
|
Pattern string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Pattern: "example.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "google.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "x.a.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "x.y.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "x.y.com",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
testCases := []struct {
|
||||||
|
Domain string
|
||||||
|
Result bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Domain: "example.com",
|
||||||
|
Result: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Domain: "y.com",
|
||||||
|
Result: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Domain: "x.y.com",
|
||||||
|
Result: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
s := NewFullMatcherSet()
|
||||||
|
for _, pattern := range patterns {
|
||||||
|
AddMatcherToSet(s, FullMatcher(pattern.Pattern))
|
||||||
|
}
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
r := s.MatchAny(testCase.Domain)
|
||||||
|
if !reflect.DeepEqual(r, testCase.Result) {
|
||||||
|
t.Error("Failed to match domain: ", testCase.Domain, ", expect ", testCase.Result, ", but got ", r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEmptyFullMatcherSet(t *testing.T) {
|
||||||
|
s := NewFullMatcherSet()
|
||||||
|
r := s.MatchAny("example.com")
|
||||||
|
if r {
|
||||||
|
t.Error("Expect false, but ", r)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package strmatcher
|
||||||
|
|
||||||
|
// SimpleMatcherSet is an implementation of MatcherSet.
|
||||||
|
// It simply stores all matchers in an array and sequentially matches them.
|
||||||
|
type SimpleMatcherSet struct {
|
||||||
|
matchers []Matcher
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddMatcher implements MatcherSetForAll.AddMatcher.
|
||||||
|
func (s *SimpleMatcherSet) AddMatcher(matcher Matcher) {
|
||||||
|
s.matchers = append(s.matchers, matcher)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchAny implements MatcherSet.MatchAny.
|
||||||
|
func (s *SimpleMatcherSet) MatchAny(input string) bool {
|
||||||
|
for _, m := range s.matchers {
|
||||||
|
if m.Match(input) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package strmatcher_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
. "github.com/xtls/xray-core/common/geodata/strmatcher"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSimpleMatcherSet(t *testing.T) {
|
||||||
|
patterns := []struct {
|
||||||
|
pattern string
|
||||||
|
mType Type
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
pattern: "example.com",
|
||||||
|
mType: Domain,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "example.com",
|
||||||
|
mType: Full,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "example.com",
|
||||||
|
mType: Regex,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cases := []struct {
|
||||||
|
input string
|
||||||
|
output bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
input: "www.example.com",
|
||||||
|
output: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "example.com",
|
||||||
|
output: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "www.e3ample.com",
|
||||||
|
output: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "xample.com",
|
||||||
|
output: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "xexample.com",
|
||||||
|
output: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "examplexcom",
|
||||||
|
output: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
matcherSet := &SimpleMatcherSet{}
|
||||||
|
for _, entry := range patterns {
|
||||||
|
matcher, err := entry.mType.New(entry.pattern)
|
||||||
|
common.Must(err)
|
||||||
|
common.Must(AddMatcherToSet(matcherSet, matcher))
|
||||||
|
}
|
||||||
|
for _, test := range cases {
|
||||||
|
if r := matcherSet.MatchAny(test.input); !reflect.DeepEqual(r, test.output) {
|
||||||
|
t.Error("unexpected output: ", r, " for test case ", test)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package strmatcher
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
// SubstrMatcherSet is implementation of MatcherSet,
|
||||||
|
// It is simply implmeneted to comply with the priority specification of Substr matchers.
|
||||||
|
type SubstrMatcherSet struct {
|
||||||
|
patterns []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddSubstrMatcher implements MatcherSetForSubstr.AddSubstrMatcher.
|
||||||
|
func (s *SubstrMatcherSet) AddSubstrMatcher(matcher SubstrMatcher) {
|
||||||
|
s.patterns = append(s.patterns, matcher.Pattern())
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchAny implements MatcherSet.MatchAny.
|
||||||
|
func (s *SubstrMatcherSet) MatchAny(input string) bool {
|
||||||
|
for _, pattern := range s.patterns {
|
||||||
|
if strings.Contains(input, pattern) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package strmatcher_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
. "github.com/xtls/xray-core/common/geodata/strmatcher"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSubstrMatcherSet(t *testing.T) {
|
||||||
|
patterns := []struct {
|
||||||
|
pattern string
|
||||||
|
mType Type
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
pattern: "apis",
|
||||||
|
mType: Substr,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "google",
|
||||||
|
mType: Substr,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "apis",
|
||||||
|
mType: Substr,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cases := []struct {
|
||||||
|
input string
|
||||||
|
output bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
input: "google.com",
|
||||||
|
output: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "apis.com",
|
||||||
|
output: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "googleapis.com",
|
||||||
|
output: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "fonts.googleapis.com",
|
||||||
|
output: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "apis.googleapis.com",
|
||||||
|
output: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "baidu.com",
|
||||||
|
output: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "goog",
|
||||||
|
output: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "api",
|
||||||
|
output: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
matcherSet := &SubstrMatcherSet{}
|
||||||
|
for _, entry := range patterns {
|
||||||
|
matcher, err := entry.mType.New(entry.pattern)
|
||||||
|
common.Must(err)
|
||||||
|
common.Must(AddMatcherToSet(matcherSet, matcher))
|
||||||
|
}
|
||||||
|
for _, test := range cases {
|
||||||
|
if r := matcherSet.MatchAny(test.input); !reflect.DeepEqual(r, test.output) {
|
||||||
|
t.Error("unexpected output: ", r, " for test case ", test)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Matcher is the interface to determine a string matches a pattern.
|
// Matcher is the interface to determine a string matches a pattern.
|
||||||
// - This is a basic matcher to represent a certain kind of match semantic(full, substr, domain or regex).
|
// - This is a basic matcher to represent a certain kind of match semantic (full, substr, domain or regex).
|
||||||
type Matcher interface {
|
type Matcher interface {
|
||||||
// Type returns the matcher's type.
|
// Type returns the matcher's type.
|
||||||
Type() Type
|
Type() Type
|
||||||
@@ -101,3 +101,21 @@ type ValueMatcher interface {
|
|||||||
// MatchAny returns true as soon as one matching matcher is found.
|
// MatchAny returns true as soon as one matching matcher is found.
|
||||||
MatchAny(input string) bool
|
MatchAny(input string) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MatcherSet is an advanced type of matcher to accept a bunch of basic Matchers (of certain type, not all matcher types).
|
||||||
|
// For example:
|
||||||
|
// - FullMatcherSet accepts FullMatcher and uses a hash table to facilitate lookup.
|
||||||
|
// - DomainMatcherSet accepts DomainMatcher and uses a trie to optimize both memory consumption and lookup speed.
|
||||||
|
type MatcherSet interface {
|
||||||
|
// MatchAny returns true as soon as one matching matcher is found.
|
||||||
|
MatchAny(input string) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnyMatcher is a lightweight matcher for callers that only need existence checks.
|
||||||
|
type AnyMatcher interface {
|
||||||
|
// Add adds a new Matcher to AnyMatcher.
|
||||||
|
Add(matcher Matcher)
|
||||||
|
|
||||||
|
// MatchAny returns true as soon as one matching matcher is found.
|
||||||
|
MatchAny(input string) bool
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package filesystem
|
package filesystem
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -26,11 +27,48 @@ func ReadFile(path string) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ReadAsset(file string) ([]byte, error) {
|
func ReadAsset(file string) ([]byte, error) {
|
||||||
return ReadFile(platform.GetAssetLocation(file))
|
path, _, err := getAssetFileLocation(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ReadFile(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
func OpenAsset(file string) (io.ReadCloser, error) {
|
func OpenAsset(file string) (io.ReadCloser, error) {
|
||||||
return NewFileReader(platform.GetAssetLocation(file))
|
path, _, err := getAssetFileLocation(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewFileReader(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func StatAsset(file string) (os.FileInfo, error) {
|
||||||
|
_, info, err := getAssetFileLocation(file)
|
||||||
|
return info, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func ResolveAsset(file string) (string, error) {
|
||||||
|
path, _, err := getAssetFileLocation(file)
|
||||||
|
return path, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAssetFileLocation(file string) (string, os.FileInfo, error) {
|
||||||
|
if !filepath.IsLocal(file) || file == "." {
|
||||||
|
return "", nil, errors.New("asset path must stay in asset directory")
|
||||||
|
}
|
||||||
|
local, err := filepath.Localize(file)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
path := platform.GetAssetLocation(local)
|
||||||
|
info, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
if !info.Mode().IsRegular() {
|
||||||
|
return "", nil, errors.New("asset is not a regular file")
|
||||||
|
}
|
||||||
|
return path, info, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReadCert(file string) ([]byte, error) {
|
func ReadCert(file string) ([]byte, error) {
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package filesystem_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "github.com/xtls/xray-core/common/platform/filesystem"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStatAssetRejectsInvalidPath(t *testing.T) {
|
||||||
|
for _, file := range []string{
|
||||||
|
"",
|
||||||
|
".",
|
||||||
|
"..",
|
||||||
|
"../geoip.dat",
|
||||||
|
"nested/..",
|
||||||
|
"nested/../geoip.dat",
|
||||||
|
"nested//geoip.dat",
|
||||||
|
"/geoip.dat",
|
||||||
|
"/tmp/geoip.dat",
|
||||||
|
`C:\geoip.dat`,
|
||||||
|
`C:geoip.dat`,
|
||||||
|
`\\server\share\geoip.dat`,
|
||||||
|
`nested\geoip.dat`,
|
||||||
|
`nested\..\geoip.dat`,
|
||||||
|
filepath.Join(t.TempDir(), "geoip.dat"),
|
||||||
|
} {
|
||||||
|
if _, err := StatAsset(file); err == nil {
|
||||||
|
t.Fatalf("expected error for %q", file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ const (
|
|||||||
UseFreedomSplice = "xray.buf.splice"
|
UseFreedomSplice = "xray.buf.splice"
|
||||||
UseVmessPadding = "xray.vmess.padding"
|
UseVmessPadding = "xray.vmess.padding"
|
||||||
UseCone = "xray.cone.disabled"
|
UseCone = "xray.cone.disabled"
|
||||||
|
UseStrictJSON = "xray.json.strict"
|
||||||
|
|
||||||
BufferSize = "xray.ray.buffer.size"
|
BufferSize = "xray.ray.buffer.size"
|
||||||
BrowserDialerAddress = "xray.browser.dialer"
|
BrowserDialerAddress = "xray.browser.dialer"
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package task
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParallelForN runs fn(0..n-1) in parallel across runtime.GOMAXPROCS(0) worker
|
||||||
|
// goroutines. Indices are partitioned into contiguous chunks so the number of
|
||||||
|
// spawned goroutines stays bounded regardless of n.
|
||||||
|
//
|
||||||
|
// fn must be safe to call concurrently from different goroutines (each call
|
||||||
|
// receives its own unique index). Output collected by writing to indexed slots
|
||||||
|
// in a pre-allocated slice is a common safe pattern.
|
||||||
|
//
|
||||||
|
// Returns the first non-nil error reported by fn; other workers may still be
|
||||||
|
// finishing briefly afterwards.
|
||||||
|
func ParallelForN(n int, fn func(i int) error) error {
|
||||||
|
if n <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
workers := max(runtime.GOMAXPROCS(0), 1)
|
||||||
|
workers = min(workers, n)
|
||||||
|
chunk := (n + workers - 1) / workers
|
||||||
|
var eg errgroup.Group
|
||||||
|
for w := range workers {
|
||||||
|
start := w * chunk
|
||||||
|
end := min(start+chunk, n)
|
||||||
|
if start >= end {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
eg.Go(func() error {
|
||||||
|
for i := start; i < end; i++ {
|
||||||
|
if err := fn(i); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return eg.Wait()
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package task_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"sync/atomic"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
. "github.com/xtls/xray-core/common/task"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParallelForN_Empty(t *testing.T) {
|
||||||
|
called := false
|
||||||
|
err := ParallelForN(0, func(i int) error {
|
||||||
|
called = true
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
common.Must(err)
|
||||||
|
if called {
|
||||||
|
t.Fatal("fn should not be called when n=0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParallelForN_AllIndicesCovered(t *testing.T) {
|
||||||
|
const N = 10000
|
||||||
|
var seen [N]int32
|
||||||
|
err := ParallelForN(N, func(i int) error {
|
||||||
|
atomic.AddInt32(&seen[i], 1)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
common.Must(err)
|
||||||
|
for i := 0; i < N; i++ {
|
||||||
|
if seen[i] != 1 {
|
||||||
|
t.Fatalf("index %d called %d times, expected 1", i, seen[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParallelForN_Error(t *testing.T) {
|
||||||
|
boom := errors.New("boom")
|
||||||
|
err := ParallelForN(1000, func(i int) error {
|
||||||
|
if i == 42 {
|
||||||
|
return boom
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != boom {
|
||||||
|
t.Fatalf("expected %v, got %v", boom, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func probeRoutes() (ipv4 bool, ipv6 bool) {
|
||||||
|
if conn, err := net.Dial("udp4", "192.33.4.12:53"); err == nil {
|
||||||
|
ipv4 = true
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
if conn, err := net.Dial("udp6", "[2001:500:2::c]:53"); err == nil {
|
||||||
|
ipv6 = true
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var routeCache struct {
|
||||||
|
sync.Once
|
||||||
|
sync.RWMutex
|
||||||
|
expire time.Time
|
||||||
|
ipv4, ipv6 bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckRoutes() (bool, bool) {
|
||||||
|
if !isGUIPlatform {
|
||||||
|
routeCache.Once.Do(func() {
|
||||||
|
routeCache.ipv4, routeCache.ipv6 = probeRoutes()
|
||||||
|
})
|
||||||
|
return routeCache.ipv4, routeCache.ipv6
|
||||||
|
}
|
||||||
|
|
||||||
|
routeCache.RWMutex.RLock()
|
||||||
|
now := time.Now()
|
||||||
|
if routeCache.expire.After(now) {
|
||||||
|
routeCache.RWMutex.RUnlock()
|
||||||
|
return routeCache.ipv4, routeCache.ipv6
|
||||||
|
}
|
||||||
|
routeCache.RWMutex.RUnlock()
|
||||||
|
|
||||||
|
routeCache.RWMutex.Lock()
|
||||||
|
defer routeCache.RWMutex.Unlock()
|
||||||
|
|
||||||
|
now = time.Now()
|
||||||
|
if routeCache.expire.After(now) { // double-check
|
||||||
|
return routeCache.ipv4, routeCache.ipv6
|
||||||
|
}
|
||||||
|
routeCache.ipv4, routeCache.ipv6 = probeRoutes() // ~2ms
|
||||||
|
routeCache.expire = now.Add(100 * time.Millisecond) // ttl
|
||||||
|
return routeCache.ipv4, routeCache.ipv6
|
||||||
|
}
|
||||||
|
|
||||||
|
var isGUIPlatform = detectGUIPlatform()
|
||||||
|
|
||||||
|
func detectGUIPlatform() bool {
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "android", "ios", "windows", "darwin":
|
||||||
|
return true
|
||||||
|
case "linux", "freebsd", "openbsd":
|
||||||
|
if t := os.Getenv("XDG_SESSION_TYPE"); t == "wayland" || t == "x11" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if os.Getenv("DISPLAY") != "" || os.Getenv("WAYLAND_DISPLAY") != "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
+2
-2
@@ -19,8 +19,8 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
Version_x byte = 26
|
Version_x byte = 26
|
||||||
Version_y byte = 4
|
Version_y byte = 5
|
||||||
Version_z byte = 17
|
Version_z byte = 9
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ func TestXrayDial(t *testing.T) {
|
|||||||
Outbound: []*core.OutboundHandlerConfig{
|
Outbound: []*core.OutboundHandlerConfig{
|
||||||
{
|
{
|
||||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||||
IpsBlocked: &freedom.IPRules{},
|
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -105,7 +105,7 @@ func TestXrayDialUDPConn(t *testing.T) {
|
|||||||
Outbound: []*core.OutboundHandlerConfig{
|
Outbound: []*core.OutboundHandlerConfig{
|
||||||
{
|
{
|
||||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||||
IpsBlocked: &freedom.IPRules{},
|
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -174,7 +174,7 @@ func TestXrayDialUDP(t *testing.T) {
|
|||||||
Outbound: []*core.OutboundHandlerConfig{
|
Outbound: []*core.OutboundHandlerConfig{
|
||||||
{
|
{
|
||||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||||
IpsBlocked: &freedom.IPRules{},
|
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
+4
-4
@@ -54,9 +54,9 @@ func TestXrayClose(t *testing.T) {
|
|||||||
Listen: net.NewIPOrDomain(net.LocalHostIP),
|
Listen: net.NewIPOrDomain(net.LocalHostIP),
|
||||||
}),
|
}),
|
||||||
ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
|
ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
|
||||||
Address: net.NewIPOrDomain(net.LocalHostIP),
|
RewriteAddress: net.NewIPOrDomain(net.LocalHostIP),
|
||||||
Port: uint32(0),
|
RewritePort: uint32(0),
|
||||||
Networks: []net.Network{net.Network_TCP},
|
AllowedNetworks: []net.Network{net.Network_TCP},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -66,7 +66,7 @@ func TestXrayClose(t *testing.T) {
|
|||||||
Receiver: &protocol.ServerEndpoint{
|
Receiver: &protocol.ServerEndpoint{
|
||||||
Address: net.NewIPOrDomain(net.LocalHostIP),
|
Address: net.NewIPOrDomain(net.LocalHostIP),
|
||||||
Port: uint32(0),
|
Port: uint32(0),
|
||||||
User: &protocol.User{
|
User: &protocol.User{
|
||||||
Account: serial.ToTypedMessage(&vmess.Account{
|
Account: serial.ToTypedMessage(&vmess.Account{
|
||||||
Id: userID.String(),
|
Id: userID.String(),
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -80,13 +80,17 @@ func New() *Client {
|
|||||||
d := &net.Dialer{
|
d := &net.Dialer{
|
||||||
Timeout: time.Second * 16,
|
Timeout: time.Second * 16,
|
||||||
Control: func(network, address string, c syscall.RawConn) error {
|
Control: func(network, address string, c syscall.RawConn) error {
|
||||||
|
var errs []error
|
||||||
for _, ctl := range internet.Controllers {
|
for _, ctl := range internet.Controllers {
|
||||||
if err := ctl(network, address, c); err != nil {
|
if err := ctl(network, address, c); err != nil {
|
||||||
errors.LogInfoInner(context.Background(), err, "failed to apply external controller")
|
errs = append(errs, err)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
err := errors.Combine(errs...)
|
||||||
|
if err != nil {
|
||||||
|
errors.LogInfoInner(context.Background(), err, "failed to apply external controller")
|
||||||
|
}
|
||||||
|
return err
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ module github.com/xtls/xray-core
|
|||||||
go 1.26
|
go 1.26
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/apernet/quic-go v0.59.1-0.20260330051153-c402ee641eb6
|
github.com/apernet/quic-go v0.59.1-0.20260425001925-6c6cc9bcb716
|
||||||
github.com/cloudflare/circl v1.6.3
|
github.com/cloudflare/circl v1.6.3
|
||||||
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344
|
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344
|
||||||
github.com/golang/mock v1.7.0-rc.1
|
github.com/golang/mock v1.7.0-rc.1
|
||||||
@@ -14,6 +14,7 @@ require (
|
|||||||
github.com/pelletier/go-toml v1.9.5
|
github.com/pelletier/go-toml v1.9.5
|
||||||
github.com/pires/go-proxyproto v0.12.0
|
github.com/pires/go-proxyproto v0.12.0
|
||||||
github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af
|
github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af
|
||||||
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/sagernet/sing v0.5.1
|
github.com/sagernet/sing v0.5.1
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.7
|
github.com/sagernet/sing-shadowsocks v0.2.7
|
||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
@@ -27,8 +28,8 @@ require (
|
|||||||
golang.org/x/sys v0.43.0
|
golang.org/x/sys v0.43.0
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb
|
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb
|
||||||
golang.zx2c4.com/wireguard/windows v0.6.1
|
golang.zx2c4.com/wireguard/windows v1.0.1
|
||||||
google.golang.org/grpc v1.80.0
|
google.golang.org/grpc v1.81.0
|
||||||
google.golang.org/protobuf v1.36.11
|
google.golang.org/protobuf v1.36.11
|
||||||
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0
|
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0
|
||||||
h12.io/socks v1.0.3
|
h12.io/socks v1.0.3
|
||||||
@@ -49,7 +50,7 @@ require (
|
|||||||
golang.org/x/text v0.36.0 // indirect
|
golang.org/x/text v0.36.0 // indirect
|
||||||
golang.org/x/time v0.12.0 // indirect
|
golang.org/x/time v0.12.0 // indirect
|
||||||
golang.org/x/tools v0.43.0 // indirect
|
golang.org/x/tools v0.43.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
|
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
|
||||||
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||||
github.com/apernet/quic-go v0.59.1-0.20260330051153-c402ee641eb6 h1:cbF95uMsQwCwAzH2i8+2lNO2TReoELLuqeeMfyBjFbY=
|
github.com/apernet/quic-go v0.59.1-0.20260425001925-6c6cc9bcb716 h1:J1O+xpLuJWkdYbw5JPGwBqIHs2J8tiEP7Py9lPqkN2I=
|
||||||
github.com/apernet/quic-go v0.59.1-0.20260330051153-c402ee641eb6/go.mod h1:Npbg8qBtAZlsAB3FWmqwlVh5jtVG6a4DlYsOylUpvzA=
|
github.com/apernet/quic-go v0.59.1-0.20260425001925-6c6cc9bcb716/go.mod h1:Npbg8qBtAZlsAB3FWmqwlVh5jtVG6a4DlYsOylUpvzA=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
|
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
|
||||||
@@ -53,6 +53,8 @@ github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
|||||||
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
||||||
github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af h1:er2acxbi3N1nvEq6HXHUAR1nTWEJmQfqiGR8EVT9rfs=
|
github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af h1:er2acxbi3N1nvEq6HXHUAR1nTWEJmQfqiGR8EVT9rfs=
|
||||||
github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
|
github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
|
||||||
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||||
github.com/sagernet/sing v0.5.1 h1:mhL/MZVq0TjuvHcpYcFtmSD1BFOxZ/+8ofbNZcg1k1Y=
|
github.com/sagernet/sing v0.5.1 h1:mhL/MZVq0TjuvHcpYcFtmSD1BFOxZ/+8ofbNZcg1k1Y=
|
||||||
@@ -70,16 +72,16 @@ github.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f/go.mod h1:DsJblcWDGt7
|
|||||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||||
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
|
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
|
||||||
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
|
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
|
||||||
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
|
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
|
||||||
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
|
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
|
||||||
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
|
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
|
||||||
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
|
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
|
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
|
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
|
||||||
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
|
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
|
||||||
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
|
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
|
||||||
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
|
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
|
||||||
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
|
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
||||||
@@ -131,14 +133,14 @@ golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeu
|
|||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A=
|
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A=
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=
|
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=
|
||||||
golang.zx2c4.com/wireguard/windows v0.6.1 h1:XMaKojH1Hs/raMrmnir4n35nTvzvWj7NmSYzHn2F4qU=
|
golang.zx2c4.com/wireguard/windows v1.0.1 h1:eOxiDVbywPC+ZQqvdCK7x+ZwWXKbYv50TtH8ysFIbw8=
|
||||||
golang.zx2c4.com/wireguard/windows v0.6.1/go.mod h1:04aqInu5GYuTFvMuDw/rKBAF7mHrltW/3rekpfbbZDM=
|
golang.zx2c4.com/wireguard/windows v1.0.1/go.mod h1:+fbT3FFdX4zzYDLwJh5+HPEcNN/3HyNdzhNSVsQM+zs=
|
||||||
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||||
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 h1:sNrWoksmOyF5bvJUcnmbeAmQi8baNhqg5IWaI3llQqU=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||||
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
|
google.golang.org/grpc v1.81.0 h1:W3G9N3KQf3BU+YuCtGKJk0CmxQNbAISICD/9AORxLIw=
|
||||||
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
|
google.golang.org/grpc v1.81.0/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I=
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
|||||||
+146
-17
@@ -1,38 +1,167 @@
|
|||||||
package conf
|
package conf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/geodata"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/proxy/dns"
|
"github.com/xtls/xray-core/proxy/dns"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type DNSOutboundRuleConfig struct {
|
||||||
|
Action string `json:"action"`
|
||||||
|
QType *PortList `json:"qtype"`
|
||||||
|
Domain *StringList `json:"domain"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DNSOutboundRuleConfig) Build() (*dns.DNSRuleConfig, error) {
|
||||||
|
rule := &dns.DNSRuleConfig{}
|
||||||
|
|
||||||
|
switch strings.ToLower(c.Action) {
|
||||||
|
case "direct":
|
||||||
|
rule.Action = dns.RuleAction_Direct
|
||||||
|
case "drop":
|
||||||
|
rule.Action = dns.RuleAction_Drop
|
||||||
|
case "reject":
|
||||||
|
rule.Action = dns.RuleAction_Reject
|
||||||
|
case "hijack":
|
||||||
|
rule.Action = dns.RuleAction_Hijack
|
||||||
|
default:
|
||||||
|
return nil, errors.New("unknown action: ", c.Action)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.QType != nil {
|
||||||
|
for _, r := range c.QType.Range {
|
||||||
|
if r.From > r.To {
|
||||||
|
return nil, errors.New("invalid qtype range: ", r.String())
|
||||||
|
}
|
||||||
|
if r.To > 65535 {
|
||||||
|
return nil, errors.New("dns rule qtype out of range: ", r.String())
|
||||||
|
}
|
||||||
|
for qtype := r.From; qtype <= r.To; qtype++ {
|
||||||
|
rule.Qtype = append(rule.Qtype, int32(qtype))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Domain != nil {
|
||||||
|
rules, err := geodata.ParseDomainRules(*c.Domain, geodata.Domain_Substr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rule.Domain = rules
|
||||||
|
}
|
||||||
|
|
||||||
|
return rule, nil
|
||||||
|
}
|
||||||
|
|
||||||
type DNSOutboundConfig struct {
|
type DNSOutboundConfig struct {
|
||||||
Network Network `json:"network"`
|
RewriteNetwork Network `json:"rewriteNetwork"`
|
||||||
Address *Address `json:"address"`
|
RewriteAddress *Address `json:"rewriteAddress"`
|
||||||
Port uint16 `json:"port"`
|
RewritePort uint16 `json:"rewritePort"`
|
||||||
UserLevel uint32 `json:"userLevel"`
|
Network Network `json:"network"`
|
||||||
NonIPQuery string `json:"nonIPQuery"`
|
Address *Address `json:"address"`
|
||||||
BlockTypes []int32 `json:"blockTypes"`
|
Port uint16 `json:"port"`
|
||||||
|
UserLevel uint32 `json:"userLevel"`
|
||||||
|
Rules []*DNSOutboundRuleConfig `json:"rules"`
|
||||||
|
NonIPQuery *string `json:"nonIPQuery"` // todo: remove legacy
|
||||||
|
BlockTypes *[]int32 `json:"blockTypes"` // todo: remove legacy
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *DNSOutboundConfig) Build() (proto.Message, error) {
|
func (c *DNSOutboundConfig) Build() (proto.Message, error) {
|
||||||
|
if len(c.Network) > 0 {
|
||||||
|
c.RewriteNetwork = c.Network
|
||||||
|
}
|
||||||
|
if c.Address != nil {
|
||||||
|
c.RewriteAddress = c.Address
|
||||||
|
}
|
||||||
|
if c.Port != 0 {
|
||||||
|
c.RewritePort = c.Port
|
||||||
|
}
|
||||||
config := &dns.Config{
|
config := &dns.Config{
|
||||||
Server: &net.Endpoint{
|
RewriteServer: &net.Endpoint{
|
||||||
Network: c.Network.Build(),
|
Network: c.RewriteNetwork.Build(),
|
||||||
Port: uint32(c.Port),
|
Port: uint32(c.RewritePort),
|
||||||
},
|
},
|
||||||
UserLevel: c.UserLevel,
|
UserLevel: c.UserLevel,
|
||||||
}
|
}
|
||||||
if c.Address != nil {
|
if c.RewriteAddress != nil {
|
||||||
config.Server.Address = c.Address.Build()
|
config.RewriteServer.Address = c.RewriteAddress.Build()
|
||||||
}
|
}
|
||||||
switch c.NonIPQuery {
|
|
||||||
case "", "reject", "drop", "skip":
|
// todo: remove legacy
|
||||||
default:
|
if c.NonIPQuery != nil || c.BlockTypes != nil {
|
||||||
return nil, errors.New(`unknown "nonIPQuery": `, c.NonIPQuery)
|
if c.Rules != nil {
|
||||||
|
return nil, errors.New("legacy nonIPQuery and blockTypes cannot be mixed with rules")
|
||||||
|
}
|
||||||
|
errors.PrintDeprecatedFeatureWarning(`"nonIPQuery" and "blockTypes"`, `"rules"`)
|
||||||
|
rules, err := c.buildLegacyDNSPolicy()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
config.Rule = rules
|
||||||
|
return config, nil
|
||||||
}
|
}
|
||||||
config.Non_IPQuery = c.NonIPQuery
|
|
||||||
config.BlockTypes = c.BlockTypes
|
for _, r := range c.Rules {
|
||||||
|
rule, err := r.Build()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
config.Rule = append(config.Rule, rule)
|
||||||
|
}
|
||||||
|
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo: remove legacy
|
||||||
|
func (c *DNSOutboundConfig) buildLegacyDNSPolicy() ([]*dns.DNSRuleConfig, error) {
|
||||||
|
rules := make([]*dns.DNSRuleConfig, 0, 3)
|
||||||
|
|
||||||
|
mode := "reject"
|
||||||
|
if c.NonIPQuery != nil && *c.NonIPQuery != "" {
|
||||||
|
mode = *c.NonIPQuery
|
||||||
|
}
|
||||||
|
switch mode {
|
||||||
|
case "", "reject", "drop", "skip":
|
||||||
|
default:
|
||||||
|
return nil, errors.New("unknown nonIPQuery: ", mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.BlockTypes != nil && len(*c.BlockTypes) > 0 {
|
||||||
|
rule := &dns.DNSRuleConfig{Action: dns.RuleAction_Drop}
|
||||||
|
if mode == "reject" {
|
||||||
|
rule.Action = dns.RuleAction_Reject
|
||||||
|
}
|
||||||
|
for _, qtype := range *c.BlockTypes {
|
||||||
|
if qtype < 0 || qtype > 65535 {
|
||||||
|
return nil, errors.New("legacy blockTypes qtype out of range: ", qtype)
|
||||||
|
}
|
||||||
|
rule.Qtype = append(rule.Qtype, qtype)
|
||||||
|
}
|
||||||
|
rules = append(rules, rule)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
rule := &dns.DNSRuleConfig{Action: dns.RuleAction_Hijack}
|
||||||
|
rule.Qtype = append(rule.Qtype, 1)
|
||||||
|
rule.Qtype = append(rule.Qtype, 28)
|
||||||
|
rules = append(rules, rule)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
rule := &dns.DNSRuleConfig{Action: dns.RuleAction_Reject}
|
||||||
|
if mode == "reject" {
|
||||||
|
rule.Action = dns.RuleAction_Reject
|
||||||
|
} else if mode == "drop" {
|
||||||
|
rule.Action = dns.RuleAction_Drop
|
||||||
|
} else if mode == "skip" {
|
||||||
|
rule.Action = dns.RuleAction_Direct
|
||||||
|
}
|
||||||
|
rules = append(rules, rule)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rules, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package conf_test
|
package conf_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common/geodata"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
. "github.com/xtls/xray-core/infra/conf"
|
. "github.com/xtls/xray-core/infra/conf"
|
||||||
"github.com/xtls/xray-core/proxy/dns"
|
"github.com/xtls/xray-core/proxy/dns"
|
||||||
@@ -22,12 +24,215 @@ func TestDnsProxyConfig(t *testing.T) {
|
|||||||
}`,
|
}`,
|
||||||
Parser: loadJSON(creator),
|
Parser: loadJSON(creator),
|
||||||
Output: &dns.Config{
|
Output: &dns.Config{
|
||||||
Server: &net.Endpoint{
|
RewriteServer: &net.Endpoint{
|
||||||
Network: net.Network_TCP,
|
Network: net.Network_TCP,
|
||||||
Address: net.NewIPOrDomain(net.IPAddress([]byte{8, 8, 8, 8})),
|
Address: net.NewIPOrDomain(net.IPAddress([]byte{8, 8, 8, 8})),
|
||||||
Port: 53,
|
Port: 53,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Input: `{
|
||||||
|
"rules": [{
|
||||||
|
"action": "direct",
|
||||||
|
"qtype": "1,3,23-24"
|
||||||
|
}, {
|
||||||
|
"action": "drop",
|
||||||
|
"qtype": 28,
|
||||||
|
"domain": ["domain:example.com", "full:example.com"]
|
||||||
|
}]
|
||||||
|
}`,
|
||||||
|
Parser: loadJSON(creator),
|
||||||
|
Output: &dns.Config{
|
||||||
|
RewriteServer: &net.Endpoint{},
|
||||||
|
Rule: []*dns.DNSRuleConfig{
|
||||||
|
{
|
||||||
|
Action: dns.RuleAction_Direct,
|
||||||
|
Qtype: []int32{1, 3, 23, 24},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Action: dns.RuleAction_Drop,
|
||||||
|
Qtype: []int32{28},
|
||||||
|
Domain: []*geodata.DomainRule{
|
||||||
|
{
|
||||||
|
Value: &geodata.DomainRule_Custom{
|
||||||
|
Custom: &geodata.Domain{
|
||||||
|
Type: geodata.Domain_Domain,
|
||||||
|
Value: "example.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: &geodata.DomainRule_Custom{
|
||||||
|
Custom: &geodata.Domain{
|
||||||
|
Type: geodata.Domain_Full,
|
||||||
|
Value: "example.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Input: `{
|
||||||
|
"rules": [{
|
||||||
|
"action": "reject",
|
||||||
|
"domain": "keyword:example"
|
||||||
|
}]
|
||||||
|
}`,
|
||||||
|
Parser: loadJSON(creator),
|
||||||
|
Output: &dns.Config{
|
||||||
|
RewriteServer: &net.Endpoint{},
|
||||||
|
Rule: []*dns.DNSRuleConfig{
|
||||||
|
{
|
||||||
|
Action: dns.RuleAction_Reject,
|
||||||
|
Domain: []*geodata.DomainRule{
|
||||||
|
{
|
||||||
|
Value: &geodata.DomainRule_Custom{
|
||||||
|
Custom: &geodata.Domain{
|
||||||
|
Type: geodata.Domain_Substr,
|
||||||
|
Value: "example",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Input: `{
|
||||||
|
"rules": [{
|
||||||
|
"action": "drop",
|
||||||
|
"qtype": 257
|
||||||
|
}]
|
||||||
|
}`,
|
||||||
|
Parser: loadJSON(creator),
|
||||||
|
Output: &dns.Config{
|
||||||
|
RewriteServer: &net.Endpoint{},
|
||||||
|
Rule: []*dns.DNSRuleConfig{
|
||||||
|
{
|
||||||
|
Action: dns.RuleAction_Drop,
|
||||||
|
Qtype: []int32{257},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo: remove legacy
|
||||||
|
func TestDnsProxyConfigLegacyCompatibility(t *testing.T) {
|
||||||
|
creator := func() Buildable {
|
||||||
|
return new(DNSOutboundConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
runMultiTestCase(t, []TestCase{
|
||||||
|
{
|
||||||
|
Input: `{
|
||||||
|
"blockTypes": []
|
||||||
|
}`,
|
||||||
|
Parser: loadJSON(creator),
|
||||||
|
Output: &dns.Config{
|
||||||
|
RewriteServer: &net.Endpoint{},
|
||||||
|
Rule: []*dns.DNSRuleConfig{
|
||||||
|
{
|
||||||
|
Action: dns.RuleAction_Hijack,
|
||||||
|
Qtype: []int32{1, 28},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Action: dns.RuleAction_Reject,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Input: `{
|
||||||
|
"blockTypes": [1, 65]
|
||||||
|
}`,
|
||||||
|
Parser: loadJSON(creator),
|
||||||
|
Output: &dns.Config{
|
||||||
|
RewriteServer: &net.Endpoint{},
|
||||||
|
Rule: []*dns.DNSRuleConfig{
|
||||||
|
{
|
||||||
|
Action: dns.RuleAction_Reject,
|
||||||
|
Qtype: []int32{1, 65},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Action: dns.RuleAction_Hijack,
|
||||||
|
Qtype: []int32{1, 28},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Action: dns.RuleAction_Reject,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Input: `{
|
||||||
|
"nonIPQuery": "drop",
|
||||||
|
"blockTypes": [1]
|
||||||
|
}`,
|
||||||
|
Parser: loadJSON(creator),
|
||||||
|
Output: &dns.Config{
|
||||||
|
RewriteServer: &net.Endpoint{},
|
||||||
|
Rule: []*dns.DNSRuleConfig{
|
||||||
|
{
|
||||||
|
Action: dns.RuleAction_Drop,
|
||||||
|
Qtype: []int32{1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Action: dns.RuleAction_Hijack,
|
||||||
|
Qtype: []int32{1, 28},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Action: dns.RuleAction_Drop,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Input: `{
|
||||||
|
"nonIPQuery": "skip",
|
||||||
|
"blockTypes": [65, 28]
|
||||||
|
}`,
|
||||||
|
Parser: loadJSON(creator),
|
||||||
|
Output: &dns.Config{
|
||||||
|
RewriteServer: &net.Endpoint{},
|
||||||
|
Rule: []*dns.DNSRuleConfig{
|
||||||
|
{
|
||||||
|
Action: dns.RuleAction_Drop,
|
||||||
|
Qtype: []int32{65, 28},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Action: dns.RuleAction_Hijack,
|
||||||
|
Qtype: []int32{1, 28},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Action: dns.RuleAction_Direct,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: remove legacy
|
||||||
|
func TestDnsProxyConfigRejectsMixedLegacyAndNewFields(t *testing.T) {
|
||||||
|
creator := func() Buildable {
|
||||||
|
return new(DNSOutboundConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := loadJSON(creator)(`{
|
||||||
|
"rules": [{
|
||||||
|
"action": "direct",
|
||||||
|
"qtype": 65
|
||||||
|
}],
|
||||||
|
"blockTypes": [65]
|
||||||
|
}`)
|
||||||
|
if err == nil || !strings.Contains(err.Error(), `legacy nonIPQuery and blockTypes cannot be mixed with rules`) {
|
||||||
|
t.Fatal("expected mixed legacy/new config error, but got ", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+18
-6
@@ -8,27 +8,39 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type DokodemoConfig struct {
|
type DokodemoConfig struct {
|
||||||
|
AllowedNetwork *NetworkList `json:"allowedNetwork"`
|
||||||
|
RewriteAddress *Address `json:"rewriteAddress"`
|
||||||
|
RewritePort uint16 `json:"rewritePort"`
|
||||||
|
Network *NetworkList `json:"network"`
|
||||||
Address *Address `json:"address"`
|
Address *Address `json:"address"`
|
||||||
Port uint16 `json:"port"`
|
Port uint16 `json:"port"`
|
||||||
PortMap map[string]string `json:"portMap"`
|
PortMap map[string]string `json:"portMap"`
|
||||||
Network *NetworkList `json:"network"`
|
|
||||||
FollowRedirect bool `json:"followRedirect"`
|
FollowRedirect bool `json:"followRedirect"`
|
||||||
UserLevel uint32 `json:"userLevel"`
|
UserLevel uint32 `json:"userLevel"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *DokodemoConfig) Build() (proto.Message, error) {
|
func (v *DokodemoConfig) Build() (proto.Message, error) {
|
||||||
config := new(dokodemo.Config)
|
if v.Network != nil {
|
||||||
if v.Address != nil {
|
v.AllowedNetwork = v.Network
|
||||||
config.Address = v.Address.Build()
|
|
||||||
}
|
}
|
||||||
config.Port = uint32(v.Port)
|
if v.Address != nil {
|
||||||
|
v.RewriteAddress = v.Address
|
||||||
|
}
|
||||||
|
if v.Port != 0 {
|
||||||
|
v.RewritePort = v.Port
|
||||||
|
}
|
||||||
|
config := new(dokodemo.Config)
|
||||||
|
config.AllowedNetworks = v.AllowedNetwork.Build()
|
||||||
|
if v.RewriteAddress != nil {
|
||||||
|
config.RewriteAddress = v.RewriteAddress.Build()
|
||||||
|
}
|
||||||
|
config.RewritePort = uint32(v.RewritePort)
|
||||||
config.PortMap = v.PortMap
|
config.PortMap = v.PortMap
|
||||||
for _, v := range config.PortMap {
|
for _, v := range config.PortMap {
|
||||||
if _, _, err := net.SplitHostPort(v); err != nil {
|
if _, _, err := net.SplitHostPort(v); err != nil {
|
||||||
return nil, errors.New("invalid portMap: ", v).Base(err)
|
return nil, errors.New("invalid portMap: ", v).Base(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
config.Networks = v.Network.Build()
|
|
||||||
config.FollowRedirect = v.FollowRedirect
|
config.FollowRedirect = v.FollowRedirect
|
||||||
config.UserLevel = v.UserLevel
|
config.UserLevel = v.UserLevel
|
||||||
return config, nil
|
return config, nil
|
||||||
|
|||||||
@@ -24,15 +24,15 @@ func TestDokodemoConfig(t *testing.T) {
|
|||||||
}`,
|
}`,
|
||||||
Parser: loadJSON(creator),
|
Parser: loadJSON(creator),
|
||||||
Output: &dokodemo.Config{
|
Output: &dokodemo.Config{
|
||||||
Address: &net.IPOrDomain{
|
RewriteAddress: &net.IPOrDomain{
|
||||||
Address: &net.IPOrDomain_Ip{
|
Address: &net.IPOrDomain_Ip{
|
||||||
Ip: []byte{8, 8, 8, 8},
|
Ip: []byte{8, 8, 8, 8},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Port: 53,
|
RewritePort: 53,
|
||||||
Networks: []net.Network{net.Network_TCP},
|
AllowedNetworks: []net.Network{net.Network_TCP},
|
||||||
FollowRedirect: true,
|
FollowRedirect: true,
|
||||||
UserLevel: 1,
|
UserLevel: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
+72
-15
@@ -1,6 +1,7 @@
|
|||||||
package conf
|
package conf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"net"
|
"net"
|
||||||
@@ -8,7 +9,7 @@ import (
|
|||||||
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/geodata"
|
"github.com/xtls/xray-core/common/geodata"
|
||||||
v2net "github.com/xtls/xray-core/common/net"
|
xnet "github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/protocol"
|
"github.com/xtls/xray-core/common/protocol"
|
||||||
"github.com/xtls/xray-core/proxy/freedom"
|
"github.com/xtls/xray-core/proxy/freedom"
|
||||||
"github.com/xtls/xray-core/transport/internet"
|
"github.com/xtls/xray-core/transport/internet"
|
||||||
@@ -16,15 +17,16 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type FreedomConfig struct {
|
type FreedomConfig struct {
|
||||||
TargetStrategy string `json:"targetStrategy"`
|
TargetStrategy string `json:"targetStrategy"`
|
||||||
DomainStrategy string `json:"domainStrategy"`
|
DomainStrategy string `json:"domainStrategy"`
|
||||||
Redirect string `json:"redirect"`
|
Redirect string `json:"redirect"`
|
||||||
UserLevel uint32 `json:"userLevel"`
|
UserLevel uint32 `json:"userLevel"`
|
||||||
Fragment *Fragment `json:"fragment"`
|
Fragment *Fragment `json:"fragment"`
|
||||||
Noise *Noise `json:"noise"`
|
Noise *Noise `json:"noise"`
|
||||||
Noises []*Noise `json:"noises"`
|
Noises []*Noise `json:"noises"`
|
||||||
ProxyProtocol uint32 `json:"proxyProtocol"`
|
ProxyProtocol uint32 `json:"proxyProtocol"`
|
||||||
IPsBlocked *StringList `json:"ipsBlocked"`
|
IPsBlocked *StringList `json:"ipsBlocked"`
|
||||||
|
FinalRules []*FreedomFinalRuleConfig `json:"finalRules"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Fragment struct {
|
type Fragment struct {
|
||||||
@@ -41,8 +43,21 @@ type Noise struct {
|
|||||||
ApplyTo string `json:"applyTo"`
|
ApplyTo string `json:"applyTo"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FreedomFinalRuleConfig struct {
|
||||||
|
Action string `json:"action"`
|
||||||
|
Network *NetworkList `json:"network"`
|
||||||
|
Port *PortList `json:"port"`
|
||||||
|
IP *StringList `json:"ip"`
|
||||||
|
BlockDelay *Int32Range `json:"blockDelay"`
|
||||||
|
}
|
||||||
|
|
||||||
// Build implements Buildable
|
// Build implements Buildable
|
||||||
func (c *FreedomConfig) Build() (proto.Message, error) {
|
func (c *FreedomConfig) Build() (proto.Message, error) {
|
||||||
|
if c.IPsBlocked != nil {
|
||||||
|
// todo: remove legacy
|
||||||
|
errors.LogWarning(context.Background(), `The feature "ipsBlocked" has been removed and migrated to "finalRules". Please update your config(s) according to release note and documentation.`)
|
||||||
|
}
|
||||||
|
|
||||||
config := new(freedom.Config)
|
config := new(freedom.Config)
|
||||||
targetStrategy := c.TargetStrategy
|
targetStrategy := c.TargetStrategy
|
||||||
if targetStrategy == "" {
|
if targetStrategy == "" {
|
||||||
@@ -142,12 +157,13 @@ func (c *FreedomConfig) Build() (proto.Message, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
config.UserLevel = c.UserLevel
|
config.UserLevel = c.UserLevel
|
||||||
|
|
||||||
if len(c.Redirect) > 0 {
|
if len(c.Redirect) > 0 {
|
||||||
host, portStr, err := net.SplitHostPort(c.Redirect)
|
host, portStr, err := net.SplitHostPort(c.Redirect)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("invalid redirect address: ", c.Redirect, ": ", err).Base(err)
|
return nil, errors.New("invalid redirect address: ", c.Redirect, ": ", err).Base(err)
|
||||||
}
|
}
|
||||||
port, err := v2net.PortFromString(portStr)
|
port, err := xnet.PortFromString(portStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("invalid redirect port: ", c.Redirect, ": ", err).Base(err)
|
return nil, errors.New("invalid redirect port: ", c.Redirect, ": ", err).Base(err)
|
||||||
}
|
}
|
||||||
@@ -158,19 +174,22 @@ func (c *FreedomConfig) Build() (proto.Message, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(host) > 0 {
|
if len(host) > 0 {
|
||||||
config.DestinationOverride.Server.Address = v2net.NewIPOrDomain(v2net.ParseAddress(host))
|
config.DestinationOverride.Server.Address = xnet.NewIPOrDomain(xnet.ParseAddress(host))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.ProxyProtocol > 0 && c.ProxyProtocol <= 2 {
|
if c.ProxyProtocol > 0 && c.ProxyProtocol <= 2 {
|
||||||
config.ProxyProtocol = c.ProxyProtocol
|
config.ProxyProtocol = c.ProxyProtocol
|
||||||
}
|
}
|
||||||
if c.IPsBlocked != nil {
|
|
||||||
rules, err := geodata.ParseIPRules(*c.IPsBlocked)
|
for _, r := range c.FinalRules {
|
||||||
|
rule, err := r.Build()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
config.IpsBlocked = &freedom.IPRules{Rules: rules}
|
config.FinalRules = append(config.FinalRules, rule)
|
||||||
}
|
}
|
||||||
|
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,3 +248,41 @@ func ParseNoise(noise *Noise) (*freedom.Noise, error) {
|
|||||||
}
|
}
|
||||||
return NConfig, nil
|
return NConfig, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *FreedomFinalRuleConfig) Build() (*freedom.FinalRuleConfig, error) {
|
||||||
|
rule := &freedom.FinalRuleConfig{}
|
||||||
|
|
||||||
|
switch strings.ToLower(c.Action) {
|
||||||
|
case "allow":
|
||||||
|
rule.Action = freedom.RuleAction_Allow
|
||||||
|
case "block":
|
||||||
|
rule.Action = freedom.RuleAction_Block
|
||||||
|
default:
|
||||||
|
return nil, errors.New("unknown action: ", c.Action)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Network != nil {
|
||||||
|
rule.Networks = c.Network.Build()
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Port != nil {
|
||||||
|
rule.PortList = c.Port.Build()
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.IP != nil {
|
||||||
|
rules, err := geodata.ParseIPRules(*c.IP)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rule.Ip = rules
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.BlockDelay != nil {
|
||||||
|
rule.BlockDelay = &freedom.Range{
|
||||||
|
Min: uint64(c.BlockDelay.From),
|
||||||
|
Max: uint64(c.BlockDelay.To),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rule, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package conf_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common/geodata"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/protocol"
|
"github.com/xtls/xray-core/common/protocol"
|
||||||
. "github.com/xtls/xray-core/infra/conf"
|
. "github.com/xtls/xray-core/infra/conf"
|
||||||
@@ -38,5 +39,64 @@ func TestFreedomConfig(t *testing.T) {
|
|||||||
UserLevel: 1,
|
UserLevel: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Input: `{
|
||||||
|
"finalRules": [{
|
||||||
|
"action": "block",
|
||||||
|
"network": "tcp,udp",
|
||||||
|
"port": "53,443",
|
||||||
|
"ip": ["10.0.0.0/8", "2001:db8::/32"],
|
||||||
|
"blockDelay": "30-60"
|
||||||
|
}, {
|
||||||
|
"action": "allow",
|
||||||
|
"network": ["udp"]
|
||||||
|
}]
|
||||||
|
}`,
|
||||||
|
Parser: loadJSON(creator),
|
||||||
|
Output: &freedom.Config{
|
||||||
|
FinalRules: []*freedom.FinalRuleConfig{
|
||||||
|
{
|
||||||
|
Action: freedom.RuleAction_Block,
|
||||||
|
Networks: []net.Network{net.Network_TCP, net.Network_UDP},
|
||||||
|
PortList: &net.PortList{
|
||||||
|
Range: []*net.PortRange{
|
||||||
|
{From: 53, To: 53},
|
||||||
|
{From: 443, To: 443},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Ip: []*geodata.IPRule{
|
||||||
|
{
|
||||||
|
Value: &geodata.IPRule_Custom{
|
||||||
|
Custom: &geodata.CIDRRule{
|
||||||
|
Cidr: &geodata.CIDR{
|
||||||
|
Ip: []byte{10, 0, 0, 0},
|
||||||
|
Prefix: 8,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: &geodata.IPRule_Custom{
|
||||||
|
Custom: &geodata.CIDRRule{
|
||||||
|
Cidr: &geodata.CIDR{
|
||||||
|
Ip: net.ParseAddress("2001:db8::").IP(),
|
||||||
|
Prefix: 32,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
BlockDelay: &freedom.Range{
|
||||||
|
Min: 30,
|
||||||
|
Max: 60,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Action: freedom.RuleAction_Allow,
|
||||||
|
Networks: []net.Network{net.Network_UDP},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package conf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/robfig/cron/v3"
|
||||||
|
"github.com/xtls/xray-core/app/geodata"
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/platform/filesystem"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GeodataAssetConfig struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
File string `json:"file"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GeodataAssetConfig) Build() (*geodata.Asset, error) {
|
||||||
|
if err := validateHTTPS(c.URL); err != nil {
|
||||||
|
return nil, errors.New("invalid geodata asset url: ", c.URL).Base(err)
|
||||||
|
}
|
||||||
|
if _, err := filesystem.StatAsset(c.File); err != nil {
|
||||||
|
return nil, errors.New("invalid geodata asset file: ", c.File).Base(err)
|
||||||
|
}
|
||||||
|
return &geodata.Asset{
|
||||||
|
Url: c.URL,
|
||||||
|
File: c.File,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateHTTPS(s string) error {
|
||||||
|
u, err := url.ParseRequestURI(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if u.Scheme != "https" || u.Host == "" {
|
||||||
|
return errors.New("scheme must be https")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type GeodataConfig struct {
|
||||||
|
Cron *string `json:"cron"`
|
||||||
|
Outbound string `json:"outbound"`
|
||||||
|
Assets []*GeodataAssetConfig `json:"assets"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GeodataConfig) Build() (proto.Message, error) {
|
||||||
|
config := &geodata.Config{}
|
||||||
|
|
||||||
|
if c.Cron != nil {
|
||||||
|
if _, err := cron.ParseStandard(*c.Cron); err != nil {
|
||||||
|
return nil, errors.New("invalid geodata cron").Base(err)
|
||||||
|
}
|
||||||
|
config.Cron = *c.Cron
|
||||||
|
}
|
||||||
|
|
||||||
|
config.Outbound = c.Outbound
|
||||||
|
|
||||||
|
assets := make([]*geodata.Asset, 0, len(c.Assets))
|
||||||
|
for _, asset := range c.Assets {
|
||||||
|
built, err := asset.Build()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
assets = append(assets, built)
|
||||||
|
}
|
||||||
|
config.Assets = assets
|
||||||
|
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package conf_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/app/geodata"
|
||||||
|
. "github.com/xtls/xray-core/infra/conf"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGeodataConfig(t *testing.T) {
|
||||||
|
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
||||||
|
|
||||||
|
creator := func() Buildable {
|
||||||
|
return new(GeodataConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
runMultiTestCase(t, []TestCase{
|
||||||
|
{
|
||||||
|
Input: `{
|
||||||
|
"cron": "0 4 * * *",
|
||||||
|
"outbound": "proxy",
|
||||||
|
"assets": [
|
||||||
|
{"url": "https://example.com/geoip.dat", "file": "geoip.dat"},
|
||||||
|
{"url": "https://example.com/geosite.dat", "file": "geosite.dat"}
|
||||||
|
]
|
||||||
|
}`,
|
||||||
|
Parser: loadJSON(creator),
|
||||||
|
Output: &geodata.Config{
|
||||||
|
Cron: "0 4 * * *",
|
||||||
|
Outbound: "proxy",
|
||||||
|
Assets: []*geodata.Asset{
|
||||||
|
{Url: "https://example.com/geoip.dat", File: "geoip.dat"},
|
||||||
|
{Url: "https://example.com/geosite.dat", File: "geosite.dat"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGeodataAssetConfig(t *testing.T) {
|
||||||
|
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
||||||
|
|
||||||
|
if _, err := (&GeodataAssetConfig{
|
||||||
|
URL: "https://example.com/geoip.dat",
|
||||||
|
File: "geoip.dat",
|
||||||
|
}).Build(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := (&GeodataAssetConfig{
|
||||||
|
URL: "https://example.com/geoip.dat",
|
||||||
|
File: "missing.dat",
|
||||||
|
}).Build(); err == nil {
|
||||||
|
t.Fatal("expected error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGeodataAssetConfigInvalidURL(t *testing.T) {
|
||||||
|
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
||||||
|
|
||||||
|
for _, rawURL := range []string{
|
||||||
|
"",
|
||||||
|
"http://example.com/geoip.dat",
|
||||||
|
"ftp://example.com/geoip.dat",
|
||||||
|
"https:///geoip.dat",
|
||||||
|
} {
|
||||||
|
if _, err := (&GeodataAssetConfig{
|
||||||
|
URL: rawURL,
|
||||||
|
File: "geoip.dat",
|
||||||
|
}).Build(); err == nil {
|
||||||
|
t.Fatalf("expected error for %q", rawURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+9
-4
@@ -23,6 +23,7 @@ func (v *HTTPAccount) Build() *http.Account {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type HTTPServerConfig struct {
|
type HTTPServerConfig struct {
|
||||||
|
Users []*HTTPAccount `json:"users"`
|
||||||
Accounts []*HTTPAccount `json:"accounts"`
|
Accounts []*HTTPAccount `json:"accounts"`
|
||||||
Transparent bool `json:"allowTransparent"`
|
Transparent bool `json:"allowTransparent"`
|
||||||
UserLevel uint32 `json:"userLevel"`
|
UserLevel uint32 `json:"userLevel"`
|
||||||
@@ -34,9 +35,13 @@ func (c *HTTPServerConfig) Build() (proto.Message, error) {
|
|||||||
UserLevel: c.UserLevel,
|
UserLevel: c.UserLevel,
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(c.Accounts) > 0 {
|
if c.Accounts != nil {
|
||||||
|
c.Users = c.Accounts
|
||||||
|
}
|
||||||
|
// TODO: PB
|
||||||
|
if len(c.Users) > 0 {
|
||||||
config.Accounts = make(map[string]string)
|
config.Accounts = make(map[string]string)
|
||||||
for _, account := range c.Accounts {
|
for _, account := range c.Users {
|
||||||
config.Accounts[account.Username] = account.Password
|
config.Accounts[account.Username] = account.Password
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,8 +56,8 @@ type HTTPRemoteConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type HTTPClientConfig struct {
|
type HTTPClientConfig struct {
|
||||||
Address *Address `json:"address"`
|
Address *Address `json:"address"`
|
||||||
Port uint16 `json:"port"`
|
Port uint16 `json:"port"`
|
||||||
Level uint32 `json:"level"`
|
Level uint32 `json:"level"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Username string `json:"user"`
|
Username string `json:"user"`
|
||||||
|
|||||||
+18
-7
@@ -4,6 +4,7 @@ import (
|
|||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/protocol"
|
"github.com/xtls/xray-core/common/protocol"
|
||||||
"github.com/xtls/xray-core/common/serial"
|
"github.com/xtls/xray-core/common/serial"
|
||||||
|
"github.com/xtls/xray-core/common/task"
|
||||||
"github.com/xtls/xray-core/proxy/hysteria"
|
"github.com/xtls/xray-core/proxy/hysteria"
|
||||||
"github.com/xtls/xray-core/proxy/hysteria/account"
|
"github.com/xtls/xray-core/proxy/hysteria/account"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
@@ -38,22 +39,32 @@ type HysteriaUserConfig struct {
|
|||||||
|
|
||||||
type HysteriaServerConfig struct {
|
type HysteriaServerConfig struct {
|
||||||
Version int32 `json:"version"`
|
Version int32 `json:"version"`
|
||||||
Users []*HysteriaUserConfig `json:"clients"`
|
Users []*HysteriaUserConfig `json:"users"`
|
||||||
|
Clients []*HysteriaUserConfig `json:"clients"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *HysteriaServerConfig) Build() (proto.Message, error) {
|
func (c *HysteriaServerConfig) Build() (proto.Message, error) {
|
||||||
config := new(hysteria.ServerConfig)
|
config := new(hysteria.ServerConfig)
|
||||||
|
|
||||||
if c.Users != nil {
|
if c.Clients != nil {
|
||||||
for _, user := range c.Users {
|
c.Users = c.Clients
|
||||||
account := &account.Account{
|
}
|
||||||
|
if len(c.Users) > 0 {
|
||||||
|
config.Users = make([]*protocol.User, len(c.Users))
|
||||||
|
processUser := func(idx int) error {
|
||||||
|
user := c.Users[idx]
|
||||||
|
acc := &account.Account{
|
||||||
Auth: user.Auth,
|
Auth: user.Auth,
|
||||||
}
|
}
|
||||||
config.Users = append(config.Users, &protocol.User{
|
config.Users[idx] = &protocol.User{
|
||||||
Email: user.Email,
|
Email: user.Email,
|
||||||
Level: user.Level,
|
Level: user.Level,
|
||||||
Account: serial.ToTypedMessage(account),
|
Account: serial.ToTypedMessage(acc),
|
||||||
})
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := task.ParallelForN(len(c.Users), processUser); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,12 +5,22 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/platform"
|
||||||
creflect "github.com/xtls/xray-core/common/reflect"
|
creflect "github.com/xtls/xray-core/common/reflect"
|
||||||
"github.com/xtls/xray-core/core"
|
"github.com/xtls/xray-core/core"
|
||||||
"github.com/xtls/xray-core/infra/conf"
|
"github.com/xtls/xray-core/infra/conf"
|
||||||
"github.com/xtls/xray-core/main/confloader"
|
"github.com/xtls/xray-core/main/confloader"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// UseStrictJSON, when true, makes JSON config decoders skip the custom
|
||||||
|
// comment-stripping reader and parse input as strict RFC 8259 JSON.
|
||||||
|
//
|
||||||
|
// Enabled by setting the env variable xray.json.strict=true (or its normalized
|
||||||
|
// form XRAY_JSON_STRICT=true). Default false preserves backward-compatible
|
||||||
|
// behavior for human-edited configs that may contain comments or other
|
||||||
|
// JSON5/JSONC syntax.
|
||||||
|
var UseStrictJSON = platform.NewEnvFlag(platform.UseStrictJSON).GetValue(func() string { return "" }) == "true"
|
||||||
|
|
||||||
func MergeConfigFromFiles(files []*core.ConfigSource) (string, error) {
|
func MergeConfigFromFiles(files []*core.ConfigSource) (string, error) {
|
||||||
c, err := mergeConfigs(files)
|
c, err := mergeConfigs(files)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -31,7 +41,11 @@ func mergeConfigs(files []*core.ConfigSource) (*conf.Config, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("failed to read config: ", file).Base(err)
|
return nil, errors.New("failed to read config: ", file).Base(err)
|
||||||
}
|
}
|
||||||
c, err := ReaderDecoderByFormat[file.Format](r)
|
decoder := ReaderDecoderByFormat[file.Format]
|
||||||
|
if file.Format == "json" && UseStrictJSON {
|
||||||
|
decoder = DecodeJSONConfigStrict
|
||||||
|
}
|
||||||
|
c, err := decoder(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("failed to decode config: ", file).Base(err)
|
return nil, errors.New("failed to decode config: ", file).Base(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ func findOffset(b []byte, o int) *offset {
|
|||||||
|
|
||||||
// DecodeJSONConfig reads from reader and decode the config into *conf.Config
|
// DecodeJSONConfig reads from reader and decode the config into *conf.Config
|
||||||
// syntax error could be detected.
|
// syntax error could be detected.
|
||||||
|
//
|
||||||
|
// Permissive: accepts JSON with Java/Python-style comments via json_reader.Reader.
|
||||||
|
// Used for local files and stdin where the config is human-edited.
|
||||||
func DecodeJSONConfig(reader io.Reader) (*conf.Config, error) {
|
func DecodeJSONConfig(reader io.Reader) (*conf.Config, error) {
|
||||||
jsonConfig := &conf.Config{}
|
jsonConfig := &conf.Config{}
|
||||||
|
|
||||||
@@ -69,6 +72,24 @@ func DecodeJSONConfig(reader io.Reader) (*conf.Config, error) {
|
|||||||
return jsonConfig, nil
|
return jsonConfig, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DecodeJSONConfigStrict reads standard RFC 8259 JSON without comment-stripping.
|
||||||
|
// Used for remote sources (http/https/http+unix) where the payload is produced by
|
||||||
|
// automated systems and cannot contain JSON5/JSONC extensions. Avoids the
|
||||||
|
// byte-by-byte comment stripper and TeeReader, which are significant overhead on
|
||||||
|
// large configs.
|
||||||
|
func DecodeJSONConfigStrict(reader io.Reader) (*conf.Config, error) {
|
||||||
|
data, err := io.ReadAll(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("failed to read config file").Base(err)
|
||||||
|
}
|
||||||
|
jsonConfig := &conf.Config{}
|
||||||
|
if err := json.Unmarshal(data, jsonConfig); err != nil {
|
||||||
|
return nil, errors.New("failed to parse remote JSON config").Base(err)
|
||||||
|
}
|
||||||
|
return jsonConfig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
func LoadJSONConfig(reader io.Reader) (*core.Config, error) {
|
func LoadJSONConfig(reader io.Reader) (*core.Config, error) {
|
||||||
jsonConfig, err := DecodeJSONConfig(reader)
|
jsonConfig, err := DecodeJSONConfig(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+40
-20
@@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/protocol"
|
"github.com/xtls/xray-core/common/protocol"
|
||||||
"github.com/xtls/xray-core/common/serial"
|
"github.com/xtls/xray-core/common/serial"
|
||||||
|
"github.com/xtls/xray-core/common/task"
|
||||||
"github.com/xtls/xray-core/proxy/shadowsocks"
|
"github.com/xtls/xray-core/proxy/shadowsocks"
|
||||||
"github.com/xtls/xray-core/proxy/shadowsocks_2022"
|
"github.com/xtls/xray-core/proxy/shadowsocks_2022"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
@@ -44,13 +45,18 @@ type ShadowsocksServerConfig struct {
|
|||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
Level byte `json:"level"`
|
Level byte `json:"level"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Users []*ShadowsocksUserConfig `json:"clients"`
|
Users []*ShadowsocksUserConfig `json:"users"`
|
||||||
|
Clients []*ShadowsocksUserConfig `json:"clients"`
|
||||||
NetworkList *NetworkList `json:"network"`
|
NetworkList *NetworkList `json:"network"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *ShadowsocksServerConfig) Build() (proto.Message, error) {
|
func (v *ShadowsocksServerConfig) Build() (proto.Message, error) {
|
||||||
errors.PrintNonRemovalDeprecatedFeatureWarning("Shadowsocks (with no Forward Secrecy, etc.)", "VLESS Encryption")
|
errors.PrintNonRemovalDeprecatedFeatureWarning("Shadowsocks (with no Forward Secrecy, etc.)", "VLESS Encryption")
|
||||||
|
|
||||||
|
if v.Clients != nil {
|
||||||
|
v.Users = v.Clients
|
||||||
|
}
|
||||||
|
|
||||||
if C.Contains(shadowaead_2022.List, v.Cipher) {
|
if C.Contains(shadowaead_2022.List, v.Cipher) {
|
||||||
return buildShadowsocks2022(v)
|
return buildShadowsocks2022(v)
|
||||||
}
|
}
|
||||||
@@ -59,23 +65,31 @@ func (v *ShadowsocksServerConfig) Build() (proto.Message, error) {
|
|||||||
config.Network = v.NetworkList.Build()
|
config.Network = v.NetworkList.Build()
|
||||||
|
|
||||||
if v.Users != nil {
|
if v.Users != nil {
|
||||||
for _, user := range v.Users {
|
if len(v.Users) > 0 {
|
||||||
account := &shadowsocks.Account{
|
config.Users = make([]*protocol.User, len(v.Users))
|
||||||
Password: user.Password,
|
processUser := func(idx int) error {
|
||||||
CipherType: cipherFromString(user.Cipher),
|
user := v.Users[idx]
|
||||||
|
account := &shadowsocks.Account{
|
||||||
|
Password: user.Password,
|
||||||
|
CipherType: cipherFromString(user.Cipher),
|
||||||
|
}
|
||||||
|
if account.Password == "" {
|
||||||
|
return errors.New("Shadowsocks password is not specified.")
|
||||||
|
}
|
||||||
|
if account.CipherType < shadowsocks.CipherType_AES_128_GCM ||
|
||||||
|
account.CipherType > shadowsocks.CipherType_XCHACHA20_POLY1305 {
|
||||||
|
return errors.New("unsupported cipher method: ", user.Cipher)
|
||||||
|
}
|
||||||
|
config.Users[idx] = &protocol.User{
|
||||||
|
Email: user.Email,
|
||||||
|
Level: uint32(user.Level),
|
||||||
|
Account: serial.ToTypedMessage(account),
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
if account.Password == "" {
|
if err := task.ParallelForN(len(v.Users), processUser); err != nil {
|
||||||
return nil, errors.New("Shadowsocks password is not specified.")
|
return nil, err
|
||||||
}
|
}
|
||||||
if account.CipherType < shadowsocks.CipherType_AES_128_GCM ||
|
|
||||||
account.CipherType > shadowsocks.CipherType_XCHACHA20_POLY1305 {
|
|
||||||
return nil, errors.New("unsupported cipher method: ", user.Cipher)
|
|
||||||
}
|
|
||||||
config.Users = append(config.Users, &protocol.User{
|
|
||||||
Email: user.Email,
|
|
||||||
Level: uint32(user.Level),
|
|
||||||
Account: serial.ToTypedMessage(account),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
account := &shadowsocks.Account{
|
account := &shadowsocks.Account{
|
||||||
@@ -121,18 +135,24 @@ func buildShadowsocks2022(v *ShadowsocksServerConfig) (proto.Message, error) {
|
|||||||
config.Key = v.Password
|
config.Key = v.Password
|
||||||
config.Network = v.NetworkList.Build()
|
config.Network = v.NetworkList.Build()
|
||||||
|
|
||||||
for _, user := range v.Users {
|
config.Users = make([]*protocol.User, len(v.Users))
|
||||||
|
processUser := func(idx int) error {
|
||||||
|
user := v.Users[idx]
|
||||||
if user.Cipher != "" {
|
if user.Cipher != "" {
|
||||||
return nil, errors.New("shadowsocks 2022 (multi-user): users must have empty method")
|
return errors.New("shadowsocks 2022 (multi-user): users must have empty method")
|
||||||
}
|
}
|
||||||
account := &shadowsocks_2022.Account{
|
account := &shadowsocks_2022.Account{
|
||||||
Key: user.Password,
|
Key: user.Password,
|
||||||
}
|
}
|
||||||
config.Users = append(config.Users, &protocol.User{
|
config.Users[idx] = &protocol.User{
|
||||||
Email: user.Email,
|
Email: user.Email,
|
||||||
Level: uint32(user.Level),
|
Level: uint32(user.Level),
|
||||||
Account: serial.ToTypedMessage(account),
|
Account: serial.ToTypedMessage(account),
|
||||||
})
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := task.ParallelForN(len(v.Users), processUser); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-3
@@ -29,6 +29,7 @@ const (
|
|||||||
|
|
||||||
type SocksServerConfig struct {
|
type SocksServerConfig struct {
|
||||||
AuthMethod string `json:"auth"`
|
AuthMethod string `json:"auth"`
|
||||||
|
Users []*SocksAccount `json:"users"`
|
||||||
Accounts []*SocksAccount `json:"accounts"`
|
Accounts []*SocksAccount `json:"accounts"`
|
||||||
UDP bool `json:"udp"`
|
UDP bool `json:"udp"`
|
||||||
Host *Address `json:"ip"`
|
Host *Address `json:"ip"`
|
||||||
@@ -47,9 +48,13 @@ func (v *SocksServerConfig) Build() (proto.Message, error) {
|
|||||||
config.AuthType = socks.AuthType_NO_AUTH
|
config.AuthType = socks.AuthType_NO_AUTH
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(v.Accounts) > 0 {
|
if v.Accounts != nil {
|
||||||
config.Accounts = make(map[string]string, len(v.Accounts))
|
v.Users = v.Accounts
|
||||||
for _, account := range v.Accounts {
|
}
|
||||||
|
// TODO: PB
|
||||||
|
if len(v.Users) > 0 {
|
||||||
|
config.Accounts = make(map[string]string, len(v.Users))
|
||||||
|
for _, account := range v.Users {
|
||||||
config.Accounts[account.Username] = account.Password
|
config.Accounts[account.Username] = account.Password
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -497,8 +497,8 @@ func (b Bandwidth) Bps() (uint64, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type UdpHop struct {
|
type UdpHop struct {
|
||||||
PortList json.RawMessage `json:"ports"`
|
PortList PortList `json:"ports"`
|
||||||
Interval *Int32Range `json:"interval"`
|
Interval Int32Range `json:"interval"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Masquerade struct {
|
type Masquerade struct {
|
||||||
@@ -2142,18 +2142,7 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
|
|||||||
return nil, errors.New("unknown congestion control: ", c.FinalMask.QuicParams.Congestion, ", valid values: reno, bbr, brutal, force-brutal")
|
return nil, errors.New("unknown congestion control: ", c.FinalMask.QuicParams.Congestion, ", valid values: reno, bbr, brutal, force-brutal")
|
||||||
}
|
}
|
||||||
|
|
||||||
var hop *PortList
|
if (c.FinalMask.QuicParams.UdpHop.Interval.From != 0 && c.FinalMask.QuicParams.UdpHop.Interval.From < 5) || (c.FinalMask.QuicParams.UdpHop.Interval.To != 0 && c.FinalMask.QuicParams.UdpHop.Interval.To < 5) {
|
||||||
if err := json.Unmarshal(c.FinalMask.QuicParams.UdpHop.PortList, &hop); err != nil {
|
|
||||||
hop = &PortList{}
|
|
||||||
}
|
|
||||||
|
|
||||||
var inertvalMin, inertvalMax int64
|
|
||||||
if c.FinalMask.QuicParams.UdpHop.Interval != nil {
|
|
||||||
inertvalMin = int64(c.FinalMask.QuicParams.UdpHop.Interval.From)
|
|
||||||
inertvalMax = int64(c.FinalMask.QuicParams.UdpHop.Interval.To)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inertvalMin != 0 && inertvalMin < 5) || (inertvalMax != 0 && inertvalMax < 5) {
|
|
||||||
return nil, errors.New("Interval must be at least 5")
|
return nil, errors.New("Interval must be at least 5")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2190,9 +2179,9 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
|
|||||||
BrutalUp: up,
|
BrutalUp: up,
|
||||||
BrutalDown: down,
|
BrutalDown: down,
|
||||||
UdpHop: &internet.UdpHop{
|
UdpHop: &internet.UdpHop{
|
||||||
Ports: hop.Build().Ports(),
|
Ports: c.FinalMask.QuicParams.UdpHop.PortList.Build().Ports(),
|
||||||
IntervalMin: inertvalMin,
|
IntervalMin: int64(c.FinalMask.QuicParams.UdpHop.Interval.From),
|
||||||
IntervalMax: inertvalMax,
|
IntervalMax: int64(c.FinalMask.QuicParams.UdpHop.Interval.To),
|
||||||
},
|
},
|
||||||
InitStreamReceiveWindow: c.FinalMask.QuicParams.InitStreamReceiveWindow,
|
InitStreamReceiveWindow: c.FinalMask.QuicParams.InitStreamReceiveWindow,
|
||||||
MaxStreamReceiveWindow: c.FinalMask.QuicParams.MaxStreamReceiveWindow,
|
MaxStreamReceiveWindow: c.FinalMask.QuicParams.MaxStreamReceiveWindow,
|
||||||
|
|||||||
+15
-4
@@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/protocol"
|
"github.com/xtls/xray-core/common/protocol"
|
||||||
"github.com/xtls/xray-core/common/serial"
|
"github.com/xtls/xray-core/common/serial"
|
||||||
|
"github.com/xtls/xray-core/common/task"
|
||||||
"github.com/xtls/xray-core/proxy/trojan"
|
"github.com/xtls/xray-core/proxy/trojan"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
@@ -111,6 +112,7 @@ type TrojanUserConfig struct {
|
|||||||
|
|
||||||
// TrojanServerConfig is Inbound configuration
|
// TrojanServerConfig is Inbound configuration
|
||||||
type TrojanServerConfig struct {
|
type TrojanServerConfig struct {
|
||||||
|
Users []*TrojanUserConfig `json:"users"`
|
||||||
Clients []*TrojanUserConfig `json:"clients"`
|
Clients []*TrojanUserConfig `json:"clients"`
|
||||||
Fallbacks []*TrojanInboundFallback `json:"fallbacks"`
|
Fallbacks []*TrojanInboundFallback `json:"fallbacks"`
|
||||||
}
|
}
|
||||||
@@ -119,13 +121,18 @@ type TrojanServerConfig struct {
|
|||||||
func (c *TrojanServerConfig) Build() (proto.Message, error) {
|
func (c *TrojanServerConfig) Build() (proto.Message, error) {
|
||||||
errors.PrintNonRemovalDeprecatedFeatureWarning("Trojan (with no Flow, etc.)", "VLESS with Flow & Seed")
|
errors.PrintNonRemovalDeprecatedFeatureWarning("Trojan (with no Flow, etc.)", "VLESS with Flow & Seed")
|
||||||
|
|
||||||
config := &trojan.ServerConfig{
|
if c.Clients != nil {
|
||||||
Users: make([]*protocol.User, len(c.Clients)),
|
c.Users = c.Clients
|
||||||
}
|
}
|
||||||
|
|
||||||
for idx, rawUser := range c.Clients {
|
config := &trojan.ServerConfig{
|
||||||
|
Users: make([]*protocol.User, len(c.Users)),
|
||||||
|
}
|
||||||
|
|
||||||
|
processClient := func(idx int) error {
|
||||||
|
rawUser := c.Users[idx]
|
||||||
if rawUser.Flow != "" {
|
if rawUser.Flow != "" {
|
||||||
return nil, errors.PrintRemovedFeatureError(`Flow for Trojan`, ``)
|
return errors.PrintRemovedFeatureError(`Flow for Trojan`, ``)
|
||||||
}
|
}
|
||||||
|
|
||||||
config.Users[idx] = &protocol.User{
|
config.Users[idx] = &protocol.User{
|
||||||
@@ -135,6 +142,10 @@ func (c *TrojanServerConfig) Build() (proto.Message, error) {
|
|||||||
Password: rawUser.Password,
|
Password: rawUser.Password,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := task.ParallelForN(len(c.Users), processClient); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, fb := range c.Fallbacks {
|
for _, fb := range c.Fallbacks {
|
||||||
|
|||||||
+22
-10
@@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/protocol"
|
"github.com/xtls/xray-core/common/protocol"
|
||||||
"github.com/xtls/xray-core/common/serial"
|
"github.com/xtls/xray-core/common/serial"
|
||||||
|
"github.com/xtls/xray-core/common/task"
|
||||||
"github.com/xtls/xray-core/common/uuid"
|
"github.com/xtls/xray-core/common/uuid"
|
||||||
"github.com/xtls/xray-core/proxy/vless"
|
"github.com/xtls/xray-core/proxy/vless"
|
||||||
"github.com/xtls/xray-core/proxy/vless/inbound"
|
"github.com/xtls/xray-core/proxy/vless/inbound"
|
||||||
@@ -30,6 +31,7 @@ type VLessInboundFallback struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type VLessInboundConfig struct {
|
type VLessInboundConfig struct {
|
||||||
|
Users []json.RawMessage `json:"users"`
|
||||||
Clients []json.RawMessage `json:"clients"`
|
Clients []json.RawMessage `json:"clients"`
|
||||||
Decryption string `json:"decryption"`
|
Decryption string `json:"decryption"`
|
||||||
Fallbacks []*VLessInboundFallback `json:"fallbacks"`
|
Fallbacks []*VLessInboundFallback `json:"fallbacks"`
|
||||||
@@ -40,25 +42,30 @@ type VLessInboundConfig struct {
|
|||||||
// Build implements Buildable
|
// Build implements Buildable
|
||||||
func (c *VLessInboundConfig) Build() (proto.Message, error) {
|
func (c *VLessInboundConfig) Build() (proto.Message, error) {
|
||||||
config := new(inbound.Config)
|
config := new(inbound.Config)
|
||||||
config.Clients = make([]*protocol.User, len(c.Clients))
|
|
||||||
|
if c.Clients != nil {
|
||||||
|
c.Users = c.Clients
|
||||||
|
}
|
||||||
|
config.Users = make([]*protocol.User, len(c.Users))
|
||||||
switch c.Flow {
|
switch c.Flow {
|
||||||
case vless.XRV, "":
|
case vless.XRV, "":
|
||||||
default:
|
default:
|
||||||
return nil, errors.New(`VLESS "settings.flow" doesn't support "` + c.Flow + `" in this version`)
|
return nil, errors.New(`VLESS "settings.flow" doesn't support "` + c.Flow + `" in this version`)
|
||||||
}
|
}
|
||||||
for idx, rawUser := range c.Clients {
|
processClient := func(idx int) error {
|
||||||
|
rawUser := c.Users[idx]
|
||||||
user := new(protocol.User)
|
user := new(protocol.User)
|
||||||
if err := json.Unmarshal(rawUser, user); err != nil {
|
if err := json.Unmarshal(rawUser, user); err != nil {
|
||||||
return nil, errors.New(`VLESS clients: invalid user`).Base(err)
|
return errors.New(`VLESS users: invalid user`).Base(err)
|
||||||
}
|
}
|
||||||
account := new(vless.Account)
|
account := new(vless.Account)
|
||||||
if err := json.Unmarshal(rawUser, account); err != nil {
|
if err := json.Unmarshal(rawUser, account); err != nil {
|
||||||
return nil, errors.New(`VLESS clients: invalid user`).Base(err)
|
return errors.New(`VLESS users: invalid user`).Base(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := uuid.ParseString(account.Id)
|
u, err := uuid.ParseString(account.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
account.Id = u.String()
|
account.Id = u.String()
|
||||||
|
|
||||||
@@ -67,7 +74,7 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) {
|
|||||||
account.Flow = c.Flow
|
account.Flow = c.Flow
|
||||||
case vless.XRV:
|
case vless.XRV:
|
||||||
default:
|
default:
|
||||||
return nil, errors.New(`VLESS clients: "flow" doesn't support "` + account.Flow + `" in this version`)
|
return errors.New(`VLESS users: "flow" doesn't support "` + account.Flow + `" in this version`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(account.Testseed) < 4 {
|
if len(account.Testseed) < 4 {
|
||||||
@@ -75,20 +82,25 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if account.Encryption != "" {
|
if account.Encryption != "" {
|
||||||
return nil, errors.New(`VLESS clients: "encryption" should not be in inbound settings`)
|
return errors.New(`VLESS users: "encryption" should not be in inbound settings`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if account.Reverse != nil {
|
if account.Reverse != nil {
|
||||||
if account.Reverse.Tag == "" {
|
if account.Reverse.Tag == "" {
|
||||||
return nil, errors.New(`VLESS clients: "tag" can't be empty for "reverse"`)
|
return errors.New(`VLESS users: "tag" can't be empty for "reverse"`)
|
||||||
}
|
}
|
||||||
if account.Reverse.Sniffing != nil { // may not be reached: error json unmarshal
|
if account.Reverse.Sniffing != nil { // may not be reached: error json unmarshal
|
||||||
return nil, errors.New(`VLESS clients: inbound's "reverse" can't have "sniffing"`)
|
return errors.New(`VLESS users: inbound's "reverse" can't have "sniffing"`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
user.Account = serial.ToTypedMessage(account)
|
user.Account = serial.ToTypedMessage(account)
|
||||||
config.Clients[idx] = user
|
config.Users[idx] = user
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := task.ParallelForN(len(c.Users), processClient); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
config.Decryption = c.Decryption
|
config.Decryption = c.Decryption
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ func TestVLessInbound(t *testing.T) {
|
|||||||
}`,
|
}`,
|
||||||
Parser: loadJSON(creator),
|
Parser: loadJSON(creator),
|
||||||
Output: &inbound.Config{
|
Output: &inbound.Config{
|
||||||
Clients: []*protocol.User{
|
Users: []*protocol.User{
|
||||||
{
|
{
|
||||||
Account: serial.ToTypedMessage(&vless.Account{
|
Account: serial.ToTypedMessage(&vless.Account{
|
||||||
Id: "27848739-7e62-4138-9fd3-098a63964b6b",
|
Id: "27848739-7e62-4138-9fd3-098a63964b6b",
|
||||||
|
|||||||
+15
-5
@@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/protocol"
|
"github.com/xtls/xray-core/common/protocol"
|
||||||
"github.com/xtls/xray-core/common/serial"
|
"github.com/xtls/xray-core/common/serial"
|
||||||
|
"github.com/xtls/xray-core/common/task"
|
||||||
"github.com/xtls/xray-core/common/uuid"
|
"github.com/xtls/xray-core/common/uuid"
|
||||||
"github.com/xtls/xray-core/proxy/vmess"
|
"github.com/xtls/xray-core/proxy/vmess"
|
||||||
"github.com/xtls/xray-core/proxy/vmess/inbound"
|
"github.com/xtls/xray-core/proxy/vmess/inbound"
|
||||||
@@ -58,7 +59,8 @@ func (c *VMessDefaultConfig) Build() *inbound.DefaultConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type VMessInboundConfig struct {
|
type VMessInboundConfig struct {
|
||||||
Users []json.RawMessage `json:"clients"`
|
Users []json.RawMessage `json:"users"`
|
||||||
|
Clients []json.RawMessage `json:"clients"`
|
||||||
Defaults *VMessDefaultConfig `json:"default"`
|
Defaults *VMessDefaultConfig `json:"default"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,25 +74,33 @@ func (c *VMessInboundConfig) Build() (proto.Message, error) {
|
|||||||
config.Default = c.Defaults.Build()
|
config.Default = c.Defaults.Build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.Clients != nil {
|
||||||
|
c.Users = c.Clients
|
||||||
|
}
|
||||||
config.User = make([]*protocol.User, len(c.Users))
|
config.User = make([]*protocol.User, len(c.Users))
|
||||||
for idx, rawData := range c.Users {
|
processUser := func(idx int) error {
|
||||||
|
rawData := c.Users[idx]
|
||||||
user := new(protocol.User)
|
user := new(protocol.User)
|
||||||
if err := json.Unmarshal(rawData, user); err != nil {
|
if err := json.Unmarshal(rawData, user); err != nil {
|
||||||
return nil, errors.New("invalid VMess user").Base(err)
|
return errors.New("invalid VMess user").Base(err)
|
||||||
}
|
}
|
||||||
account := new(VMessAccount)
|
account := new(VMessAccount)
|
||||||
if err := json.Unmarshal(rawData, account); err != nil {
|
if err := json.Unmarshal(rawData, account); err != nil {
|
||||||
return nil, errors.New("invalid VMess user").Base(err)
|
return errors.New("invalid VMess user").Base(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := uuid.ParseString(account.ID)
|
u, err := uuid.ParseString(account.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
account.ID = u.String()
|
account.ID = u.String()
|
||||||
|
|
||||||
user.Account = serial.ToTypedMessage(account.Build())
|
user.Account = serial.ToTypedMessage(account.Build())
|
||||||
config.User[idx] = user
|
config.User[idx] = user
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := task.ParallelForN(len(c.Users), processUser); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return config, nil
|
return config, nil
|
||||||
|
|||||||
@@ -361,6 +361,7 @@ type Config struct {
|
|||||||
Observatory *ObservatoryConfig `json:"observatory"`
|
Observatory *ObservatoryConfig `json:"observatory"`
|
||||||
BurstObservatory *BurstObservatoryConfig `json:"burstObservatory"`
|
BurstObservatory *BurstObservatoryConfig `json:"burstObservatory"`
|
||||||
Version *VersionConfig `json:"version"`
|
Version *VersionConfig `json:"version"`
|
||||||
|
Geodata *GeodataConfig `json:"geodata"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) findInboundTag(tag string) int {
|
func (c *Config) findInboundTag(tag string) int {
|
||||||
@@ -433,6 +434,10 @@ func (c *Config) Override(o *Config, fn string) {
|
|||||||
c.Version = o.Version
|
c.Version = o.Version
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if o.Geodata != nil {
|
||||||
|
c.Geodata = o.Geodata
|
||||||
|
}
|
||||||
|
|
||||||
// update the Inbound in slice if the only one in override config has same tag
|
// update the Inbound in slice if the only one in override config has same tag
|
||||||
if len(o.InboundConfigs) > 0 {
|
if len(o.InboundConfigs) > 0 {
|
||||||
for i := range o.InboundConfigs {
|
for i := range o.InboundConfigs {
|
||||||
@@ -542,6 +547,7 @@ func (c *Config) Build() (*core.Config, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if c.Reverse != nil {
|
if c.Reverse != nil {
|
||||||
|
return nil, errors.PrintRemovedFeatureError(`"legacy reverse"`, `"VLESS Reverse Proxy"`)
|
||||||
r, err := c.Reverse.Build()
|
r, err := c.Reverse.Build()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("failed to build reverse configuration").Base(err)
|
return nil, errors.New("failed to build reverse configuration").Base(err)
|
||||||
@@ -581,6 +587,14 @@ func (c *Config) Build() (*core.Config, error) {
|
|||||||
config.App = append(config.App, serial.ToTypedMessage(r))
|
config.App = append(config.App, serial.ToTypedMessage(r))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.Geodata != nil {
|
||||||
|
r, err := c.Geodata.Build()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("failed to build geodata configuration").Base(err)
|
||||||
|
}
|
||||||
|
config.App = append(config.App, serial.ToTypedMessage(r))
|
||||||
|
}
|
||||||
|
|
||||||
var inbounds []InboundDetourConfig
|
var inbounds []InboundDetourConfig
|
||||||
|
|
||||||
if len(c.InboundConfigs) > 0 {
|
if len(c.InboundConfigs) > 0 {
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ func extractInboundUsers(inb *core.InboundHandlerConfig) []*protocol.User {
|
|||||||
case *vmessin.Config:
|
case *vmessin.Config:
|
||||||
return ty.User
|
return ty.User
|
||||||
case *vlessin.Config:
|
case *vlessin.Config:
|
||||||
return ty.Clients
|
return ty.Users
|
||||||
case *trojan.ServerConfig:
|
case *trojan.ServerConfig:
|
||||||
return ty.Users
|
return ty.Users
|
||||||
case *shadowsocks.ServerConfig:
|
case *shadowsocks.ServerConfig:
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import (
|
|||||||
// Other optional features.
|
// Other optional features.
|
||||||
_ "github.com/xtls/xray-core/app/dns"
|
_ "github.com/xtls/xray-core/app/dns"
|
||||||
_ "github.com/xtls/xray-core/app/dns/fakedns"
|
_ "github.com/xtls/xray-core/app/dns/fakedns"
|
||||||
|
_ "github.com/xtls/xray-core/app/geodata"
|
||||||
_ "github.com/xtls/xray-core/app/log"
|
_ "github.com/xtls/xray-core/app/log"
|
||||||
_ "github.com/xtls/xray-core/app/metrics"
|
_ "github.com/xtls/xray-core/app/metrics"
|
||||||
_ "github.com/xtls/xray-core/app/policy"
|
_ "github.com/xtls/xray-core/app/policy"
|
||||||
|
|||||||
@@ -41,6 +41,13 @@ func init() {
|
|||||||
}
|
}
|
||||||
return cf.Build()
|
return cf.Build()
|
||||||
case io.Reader:
|
case io.Reader:
|
||||||
|
if serial.UseStrictJSON {
|
||||||
|
cfg, err := serial.DecodeJSONConfigStrict(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cfg.Build()
|
||||||
|
}
|
||||||
return serial.LoadJSONConfig(v)
|
return serial.LoadJSONConfig(v)
|
||||||
default:
|
default:
|
||||||
return nil, errors.New("unknown type")
|
return nil, errors.New("unknown type")
|
||||||
|
|||||||
+22
-1
@@ -91,7 +91,28 @@ func executeRun(cmd *base.Command, args []string) {
|
|||||||
fmt.Println("Failed to start:", err)
|
fmt.Println("Failed to start:", err)
|
||||||
os.Exit(-1)
|
os.Exit(-1)
|
||||||
}
|
}
|
||||||
defer server.Close()
|
defer func() {
|
||||||
|
closeErrCh := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
closeErrCh <- server.Close()
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case err := <-closeErrCh:
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Failed to close server:", err)
|
||||||
|
}
|
||||||
|
case <-time.After(10 * time.Second):
|
||||||
|
fmt.Println("Timeout when closing, printing traces:")
|
||||||
|
buf := make([]byte, 1<<20)
|
||||||
|
n := runtime.Stack(buf, true)
|
||||||
|
blocks := strings.Split(string(buf[:n]), "\n\n")
|
||||||
|
for _, block := range blocks {
|
||||||
|
if strings.Contains(block, "github.com/xtls/xray-core/core.(*Instance).Close") {
|
||||||
|
fmt.Println(block)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
// Explicitly triggering GC to remove garbage from config loading.
|
// Explicitly triggering GC to remove garbage from config loading.
|
||||||
runtime.GC()
|
runtime.GC()
|
||||||
|
|||||||
@@ -6,7 +6,11 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"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/dice"
|
||||||
|
"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/common/signal"
|
||||||
"github.com/xtls/xray-core/transport"
|
"github.com/xtls/xray-core/transport"
|
||||||
"github.com/xtls/xray-core/transport/internet"
|
"github.com/xtls/xray-core/transport/internet"
|
||||||
)
|
)
|
||||||
@@ -38,7 +42,17 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
|||||||
// Sleep a little here to make sure the response is sent to client.
|
// Sleep a little here to make sure the response is sent to client.
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
}
|
}
|
||||||
common.Interrupt(link.Writer)
|
defer common.Interrupt(link.Writer)
|
||||||
|
defer common.Interrupt(link.Reader)
|
||||||
|
// wait to drain all the possible incoming UDP data
|
||||||
|
if ob.Target.Network == net.Network_UDP {
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
timer := signal.CancelAfterInactivity(ctx, func() {
|
||||||
|
cancel()
|
||||||
|
}, time.Duration(30+dice.Roll(61))*time.Second)
|
||||||
|
go buf.Copy(link.Reader, buf.Discard, buf.UpdateActivity(timer))
|
||||||
|
<-ctx.Done()
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+162
-41
@@ -7,6 +7,7 @@
|
|||||||
package dns
|
package dns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
geodata "github.com/xtls/xray-core/common/geodata"
|
||||||
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,21 +23,130 @@ const (
|
|||||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type RuleAction int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
RuleAction_Direct RuleAction = 0
|
||||||
|
RuleAction_Drop RuleAction = 1
|
||||||
|
RuleAction_Reject RuleAction = 2
|
||||||
|
RuleAction_Hijack RuleAction = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
// Enum value maps for RuleAction.
|
||||||
|
var (
|
||||||
|
RuleAction_name = map[int32]string{
|
||||||
|
0: "Direct",
|
||||||
|
1: "Drop",
|
||||||
|
2: "Reject",
|
||||||
|
3: "Hijack",
|
||||||
|
}
|
||||||
|
RuleAction_value = map[string]int32{
|
||||||
|
"Direct": 0,
|
||||||
|
"Drop": 1,
|
||||||
|
"Reject": 2,
|
||||||
|
"Hijack": 3,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (x RuleAction) Enum() *RuleAction {
|
||||||
|
p := new(RuleAction)
|
||||||
|
*p = x
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x RuleAction) String() string {
|
||||||
|
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (RuleAction) Descriptor() protoreflect.EnumDescriptor {
|
||||||
|
return file_proxy_dns_config_proto_enumTypes[0].Descriptor()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (RuleAction) Type() protoreflect.EnumType {
|
||||||
|
return &file_proxy_dns_config_proto_enumTypes[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x RuleAction) Number() protoreflect.EnumNumber {
|
||||||
|
return protoreflect.EnumNumber(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use RuleAction.Descriptor instead.
|
||||||
|
func (RuleAction) EnumDescriptor() ([]byte, []int) {
|
||||||
|
return file_proxy_dns_config_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
type DNSRuleConfig struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Action RuleAction `protobuf:"varint,1,opt,name=action,proto3,enum=xray.proxy.dns.RuleAction" json:"action,omitempty"`
|
||||||
|
Qtype []int32 `protobuf:"varint,2,rep,packed,name=qtype,proto3" json:"qtype,omitempty"`
|
||||||
|
Domain []*geodata.DomainRule `protobuf:"bytes,3,rep,name=domain,proto3" json:"domain,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DNSRuleConfig) Reset() {
|
||||||
|
*x = DNSRuleConfig{}
|
||||||
|
mi := &file_proxy_dns_config_proto_msgTypes[0]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DNSRuleConfig) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*DNSRuleConfig) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *DNSRuleConfig) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_proxy_dns_config_proto_msgTypes[0]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use DNSRuleConfig.ProtoReflect.Descriptor instead.
|
||||||
|
func (*DNSRuleConfig) Descriptor() ([]byte, []int) {
|
||||||
|
return file_proxy_dns_config_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DNSRuleConfig) GetAction() RuleAction {
|
||||||
|
if x != nil {
|
||||||
|
return x.Action
|
||||||
|
}
|
||||||
|
return RuleAction_Direct
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DNSRuleConfig) GetQtype() []int32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Qtype
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DNSRuleConfig) GetDomain() []*geodata.DomainRule {
|
||||||
|
if x != nil {
|
||||||
|
return x.Domain
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
// Server is the DNS server address. If specified, this address overrides the
|
UserLevel uint32 `protobuf:"varint,1,opt,name=user_level,json=userLevel,proto3" json:"user_level,omitempty"`
|
||||||
// original one.
|
Rule []*DNSRuleConfig `protobuf:"bytes,2,rep,name=rule,proto3" json:"rule,omitempty"`
|
||||||
Server *net.Endpoint `protobuf:"bytes,1,opt,name=server,proto3" json:"server,omitempty"`
|
RewriteServer *net.Endpoint `protobuf:"bytes,3,opt,name=rewrite_server,json=rewriteServer,proto3" json:"rewrite_server,omitempty"`
|
||||||
UserLevel uint32 `protobuf:"varint,2,opt,name=user_level,json=userLevel,proto3" json:"user_level,omitempty"`
|
|
||||||
Non_IPQuery string `protobuf:"bytes,3,opt,name=non_IP_query,json=nonIPQuery,proto3" json:"non_IP_query,omitempty"`
|
|
||||||
BlockTypes []int32 `protobuf:"varint,4,rep,packed,name=block_types,json=blockTypes,proto3" json:"block_types,omitempty"`
|
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Config) Reset() {
|
func (x *Config) Reset() {
|
||||||
*x = Config{}
|
*x = Config{}
|
||||||
mi := &file_proxy_dns_config_proto_msgTypes[0]
|
mi := &file_proxy_dns_config_proto_msgTypes[1]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -48,7 +158,7 @@ func (x *Config) String() string {
|
|||||||
func (*Config) ProtoMessage() {}
|
func (*Config) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *Config) ProtoReflect() protoreflect.Message {
|
func (x *Config) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_proxy_dns_config_proto_msgTypes[0]
|
mi := &file_proxy_dns_config_proto_msgTypes[1]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -61,14 +171,7 @@ func (x *Config) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
|
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
|
||||||
func (*Config) Descriptor() ([]byte, []int) {
|
func (*Config) Descriptor() ([]byte, []int) {
|
||||||
return file_proxy_dns_config_proto_rawDescGZIP(), []int{0}
|
return file_proxy_dns_config_proto_rawDescGZIP(), []int{1}
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Config) GetServer() *net.Endpoint {
|
|
||||||
if x != nil {
|
|
||||||
return x.Server
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Config) GetUserLevel() uint32 {
|
func (x *Config) GetUserLevel() uint32 {
|
||||||
@@ -78,16 +181,16 @@ func (x *Config) GetUserLevel() uint32 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Config) GetNon_IPQuery() string {
|
func (x *Config) GetRule() []*DNSRuleConfig {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Non_IPQuery
|
return x.Rule
|
||||||
}
|
}
|
||||||
return ""
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Config) GetBlockTypes() []int32 {
|
func (x *Config) GetRewriteServer() *net.Endpoint {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.BlockTypes
|
return x.RewriteServer
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -96,15 +199,25 @@ var File_proxy_dns_config_proto protoreflect.FileDescriptor
|
|||||||
|
|
||||||
const file_proxy_dns_config_proto_rawDesc = "" +
|
const file_proxy_dns_config_proto_rawDesc = "" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"\x16proxy/dns/config.proto\x12\x0exray.proxy.dns\x1a\x1ccommon/net/destination.proto\"\x9d\x01\n" +
|
"\x16proxy/dns/config.proto\x12\x0exray.proxy.dns\x1a\x1ccommon/net/destination.proto\x1a\x1bcommon/geodata/geodat.proto\"\x92\x01\n" +
|
||||||
"\x06Config\x121\n" +
|
"\rDNSRuleConfig\x122\n" +
|
||||||
"\x06server\x18\x01 \x01(\v2\x19.xray.common.net.EndpointR\x06server\x12\x1d\n" +
|
"\x06action\x18\x01 \x01(\x0e2\x1a.xray.proxy.dns.RuleActionR\x06action\x12\x14\n" +
|
||||||
|
"\x05qtype\x18\x02 \x03(\x05R\x05qtype\x127\n" +
|
||||||
|
"\x06domain\x18\x03 \x03(\v2\x1f.xray.common.geodata.DomainRuleR\x06domain\"\x9c\x01\n" +
|
||||||
|
"\x06Config\x12\x1d\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"user_level\x18\x02 \x01(\rR\tuserLevel\x12 \n" +
|
"user_level\x18\x01 \x01(\rR\tuserLevel\x121\n" +
|
||||||
"\fnon_IP_query\x18\x03 \x01(\tR\n" +
|
"\x04rule\x18\x02 \x03(\v2\x1d.xray.proxy.dns.DNSRuleConfigR\x04rule\x12@\n" +
|
||||||
"nonIPQuery\x12\x1f\n" +
|
"\x0erewrite_server\x18\x03 \x01(\v2\x19.xray.common.net.EndpointR\rrewriteServer*:\n" +
|
||||||
"\vblock_types\x18\x04 \x03(\x05R\n" +
|
"\n" +
|
||||||
"blockTypesBL\n" +
|
"RuleAction\x12\n" +
|
||||||
|
"\n" +
|
||||||
|
"\x06Direct\x10\x00\x12\b\n" +
|
||||||
|
"\x04Drop\x10\x01\x12\n" +
|
||||||
|
"\n" +
|
||||||
|
"\x06Reject\x10\x02\x12\n" +
|
||||||
|
"\n" +
|
||||||
|
"\x06Hijack\x10\x03BL\n" +
|
||||||
"\x12com.xray.proxy.dnsP\x01Z#github.com/xtls/xray-core/proxy/dns\xaa\x02\x0eXray.Proxy.Dnsb\x06proto3"
|
"\x12com.xray.proxy.dnsP\x01Z#github.com/xtls/xray-core/proxy/dns\xaa\x02\x0eXray.Proxy.Dnsb\x06proto3"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -119,18 +232,25 @@ func file_proxy_dns_config_proto_rawDescGZIP() []byte {
|
|||||||
return file_proxy_dns_config_proto_rawDescData
|
return file_proxy_dns_config_proto_rawDescData
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_proxy_dns_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
var file_proxy_dns_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||||
|
var file_proxy_dns_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||||
var file_proxy_dns_config_proto_goTypes = []any{
|
var file_proxy_dns_config_proto_goTypes = []any{
|
||||||
(*Config)(nil), // 0: xray.proxy.dns.Config
|
(RuleAction)(0), // 0: xray.proxy.dns.RuleAction
|
||||||
(*net.Endpoint)(nil), // 1: xray.common.net.Endpoint
|
(*DNSRuleConfig)(nil), // 1: xray.proxy.dns.DNSRuleConfig
|
||||||
|
(*Config)(nil), // 2: xray.proxy.dns.Config
|
||||||
|
(*geodata.DomainRule)(nil), // 3: xray.common.geodata.DomainRule
|
||||||
|
(*net.Endpoint)(nil), // 4: xray.common.net.Endpoint
|
||||||
}
|
}
|
||||||
var file_proxy_dns_config_proto_depIdxs = []int32{
|
var file_proxy_dns_config_proto_depIdxs = []int32{
|
||||||
1, // 0: xray.proxy.dns.Config.server:type_name -> xray.common.net.Endpoint
|
0, // 0: xray.proxy.dns.DNSRuleConfig.action:type_name -> xray.proxy.dns.RuleAction
|
||||||
1, // [1:1] is the sub-list for method output_type
|
3, // 1: xray.proxy.dns.DNSRuleConfig.domain:type_name -> xray.common.geodata.DomainRule
|
||||||
1, // [1:1] is the sub-list for method input_type
|
1, // 2: xray.proxy.dns.Config.rule:type_name -> xray.proxy.dns.DNSRuleConfig
|
||||||
1, // [1:1] is the sub-list for extension type_name
|
4, // 3: xray.proxy.dns.Config.rewrite_server:type_name -> xray.common.net.Endpoint
|
||||||
1, // [1:1] is the sub-list for extension extendee
|
4, // [4:4] is the sub-list for method output_type
|
||||||
0, // [0:1] is the sub-list for field type_name
|
4, // [4:4] is the sub-list for method input_type
|
||||||
|
4, // [4:4] is the sub-list for extension type_name
|
||||||
|
4, // [4:4] is the sub-list for extension extendee
|
||||||
|
0, // [0:4] is the sub-list for field type_name
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { file_proxy_dns_config_proto_init() }
|
func init() { file_proxy_dns_config_proto_init() }
|
||||||
@@ -143,13 +263,14 @@ func file_proxy_dns_config_proto_init() {
|
|||||||
File: protoimpl.DescBuilder{
|
File: protoimpl.DescBuilder{
|
||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_dns_config_proto_rawDesc), len(file_proxy_dns_config_proto_rawDesc)),
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_dns_config_proto_rawDesc), len(file_proxy_dns_config_proto_rawDesc)),
|
||||||
NumEnums: 0,
|
NumEnums: 1,
|
||||||
NumMessages: 1,
|
NumMessages: 2,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 0,
|
NumServices: 0,
|
||||||
},
|
},
|
||||||
GoTypes: file_proxy_dns_config_proto_goTypes,
|
GoTypes: file_proxy_dns_config_proto_goTypes,
|
||||||
DependencyIndexes: file_proxy_dns_config_proto_depIdxs,
|
DependencyIndexes: file_proxy_dns_config_proto_depIdxs,
|
||||||
|
EnumInfos: file_proxy_dns_config_proto_enumTypes,
|
||||||
MessageInfos: file_proxy_dns_config_proto_msgTypes,
|
MessageInfos: file_proxy_dns_config_proto_msgTypes,
|
||||||
}.Build()
|
}.Build()
|
||||||
File_proxy_dns_config_proto = out.File
|
File_proxy_dns_config_proto = out.File
|
||||||
|
|||||||
+17
-6
@@ -7,12 +7,23 @@ option java_package = "com.xray.proxy.dns";
|
|||||||
option java_multiple_files = true;
|
option java_multiple_files = true;
|
||||||
|
|
||||||
import "common/net/destination.proto";
|
import "common/net/destination.proto";
|
||||||
|
import "common/geodata/geodat.proto";
|
||||||
|
|
||||||
|
enum RuleAction {
|
||||||
|
Direct = 0;
|
||||||
|
Drop = 1;
|
||||||
|
Reject = 2;
|
||||||
|
Hijack = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DNSRuleConfig {
|
||||||
|
RuleAction action = 1;
|
||||||
|
repeated int32 qtype = 2;
|
||||||
|
repeated xray.common.geodata.DomainRule domain = 3;
|
||||||
|
}
|
||||||
|
|
||||||
message Config {
|
message Config {
|
||||||
// Server is the DNS server address. If specified, this address overrides the
|
uint32 user_level = 1;
|
||||||
// original one.
|
repeated DNSRuleConfig rule = 2;
|
||||||
xray.common.net.Endpoint server = 1;
|
xray.common.net.Endpoint rewrite_server = 3;
|
||||||
uint32 user_level = 2;
|
|
||||||
string non_IP_query = 3;
|
|
||||||
repeated int32 block_types = 4;
|
|
||||||
}
|
}
|
||||||
|
|||||||
+107
-59
@@ -11,6 +11,7 @@ import (
|
|||||||
"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/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/geodata"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
dns_proto "github.com/xtls/xray-core/common/protocol/dns"
|
dns_proto "github.com/xtls/xray-core/common/protocol/dns"
|
||||||
"github.com/xtls/xray-core/common/session"
|
"github.com/xtls/xray-core/common/session"
|
||||||
@@ -40,6 +41,31 @@ func init() {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DNSRule struct {
|
||||||
|
action RuleAction
|
||||||
|
qTypes []uint16
|
||||||
|
domains geodata.DomainMatcher
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DNSRule) matchQType(qType uint16) bool {
|
||||||
|
if len(r.qTypes) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for _, t := range r.qTypes {
|
||||||
|
if t == qType {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DNSRule) Apply(qType uint16, domain string) bool {
|
||||||
|
if !r.matchQType(qType) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return r.domains == nil || r.domains.MatchAny(strings.TrimSuffix(strings.ToLower(domain), "."))
|
||||||
|
}
|
||||||
|
|
||||||
type ownLinkVerifier interface {
|
type ownLinkVerifier interface {
|
||||||
IsOwnLink(ctx context.Context) bool
|
IsOwnLink(ctx context.Context) bool
|
||||||
}
|
}
|
||||||
@@ -48,10 +74,9 @@ type Handler struct {
|
|||||||
client dns.Client
|
client dns.Client
|
||||||
fdns dns.FakeDNSEngine
|
fdns dns.FakeDNSEngine
|
||||||
ownLinkVerifier ownLinkVerifier
|
ownLinkVerifier ownLinkVerifier
|
||||||
server net.Destination
|
rewriteServer net.Destination
|
||||||
timeout time.Duration
|
timeout time.Duration
|
||||||
nonIPQuery string
|
rules []*DNSRule
|
||||||
blockTypes []int32
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) Init(config *Config, dnsClient dns.Client, policyManager policy.Manager) error {
|
func (h *Handler) Init(config *Config, dnsClient dns.Client, policyManager policy.Manager) error {
|
||||||
@@ -62,14 +87,29 @@ func (h *Handler) Init(config *Config, dnsClient dns.Client, policyManager polic
|
|||||||
h.ownLinkVerifier = v
|
h.ownLinkVerifier = v
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.Server != nil {
|
if config.RewriteServer != nil {
|
||||||
h.server = config.Server.AsDestination()
|
h.rewriteServer = config.RewriteServer.AsDestination()
|
||||||
}
|
}
|
||||||
h.nonIPQuery = config.Non_IPQuery
|
|
||||||
if h.nonIPQuery == "" {
|
h.rules = make([]*DNSRule, 0, len(config.Rule))
|
||||||
h.nonIPQuery = "reject"
|
for _, r := range config.Rule {
|
||||||
|
rule := &DNSRule{
|
||||||
|
action: r.Action,
|
||||||
|
qTypes: make([]uint16, 0, len(r.Qtype)),
|
||||||
|
}
|
||||||
|
for _, t := range r.Qtype {
|
||||||
|
rule.qTypes = append(rule.qTypes, uint16(t))
|
||||||
|
}
|
||||||
|
if len(r.Domain) > 0 {
|
||||||
|
m, err := geodata.DomainReg.BuildDomainMatcher(r.Domain)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rule.domains = m
|
||||||
|
}
|
||||||
|
h.rules = append(h.rules, rule)
|
||||||
}
|
}
|
||||||
h.blockTypes = config.BlockTypes
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,30 +117,38 @@ func (h *Handler) isOwnLink(ctx context.Context) bool {
|
|||||||
return h.ownLinkVerifier != nil && h.ownLinkVerifier.IsOwnLink(ctx)
|
return h.ownLinkVerifier != nil && h.ownLinkVerifier.IsOwnLink(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseIPQuery(b []byte) (r bool, domain string, id uint16, qType dnsmessage.Type) {
|
func parseQuery(b []byte) (id uint16, qType dnsmessage.Type, domain string, ok bool) {
|
||||||
var parser dnsmessage.Parser
|
var parser dnsmessage.Parser
|
||||||
header, err := parser.Start(b)
|
header, err := parser.Start(b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errors.LogInfoInner(context.Background(), err, "parser start")
|
errors.LogInfoInner(context.Background(), err, "parser start")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
id = header.ID
|
id = header.ID
|
||||||
q, err := parser.Question()
|
q, err := parser.Question()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errors.LogInfoInner(context.Background(), err, "question")
|
errors.LogInfoInner(context.Background(), err, "question")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
domain = q.Name.String()
|
|
||||||
qType = q.Type
|
qType = q.Type
|
||||||
if qType != dnsmessage.TypeA && qType != dnsmessage.TypeAAAA {
|
domain = q.Name.String()
|
||||||
return
|
ok = true
|
||||||
}
|
|
||||||
|
|
||||||
r = true
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Handler) applyRules(qType dnsmessage.Type, domain string) RuleAction {
|
||||||
|
qCode := uint16(qType)
|
||||||
|
for _, r := range h.rules {
|
||||||
|
if r.Apply(qCode, domain) {
|
||||||
|
return r.action
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if qType == dnsmessage.TypeA || qType == dnsmessage.TypeAAAA {
|
||||||
|
return RuleAction_Hijack
|
||||||
|
}
|
||||||
|
return RuleAction_Reject
|
||||||
|
}
|
||||||
|
|
||||||
// Process implements proxy.Outbound.
|
// Process implements proxy.Outbound.
|
||||||
func (h *Handler) Process(ctx context.Context, link *transport.Link, d internet.Dialer) error {
|
func (h *Handler) Process(ctx context.Context, link *transport.Link, d internet.Dialer) error {
|
||||||
outbounds := session.OutboundsFromContext(ctx)
|
outbounds := session.OutboundsFromContext(ctx)
|
||||||
@@ -113,14 +161,14 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, d internet.
|
|||||||
srcNetwork := ob.Target.Network
|
srcNetwork := ob.Target.Network
|
||||||
|
|
||||||
dest := ob.Target
|
dest := ob.Target
|
||||||
if h.server.Network != net.Network_Unknown {
|
if h.rewriteServer.Network != net.Network_Unknown {
|
||||||
dest.Network = h.server.Network
|
dest.Network = h.rewriteServer.Network
|
||||||
}
|
}
|
||||||
if h.server.Address != nil {
|
if h.rewriteServer.Address != nil {
|
||||||
dest.Address = h.server.Address
|
dest.Address = h.rewriteServer.Address
|
||||||
}
|
}
|
||||||
if h.server.Port != 0 {
|
if h.rewriteServer.Port != 0 {
|
||||||
dest.Port = h.server.Port
|
dest.Port = h.rewriteServer.Port
|
||||||
}
|
}
|
||||||
|
|
||||||
errors.LogInfo(ctx, "handling DNS traffic to ", dest)
|
errors.LogInfo(ctx, "handling DNS traffic to ", dest)
|
||||||
@@ -183,51 +231,51 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, d internet.
|
|||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
timer.Update()
|
timer.Update()
|
||||||
|
|
||||||
if !h.isOwnLink(ctx) {
|
if h.isOwnLink(ctx) {
|
||||||
isIPQuery, domain, id, qType := parseIPQuery(b.Bytes())
|
if err := connWriter.WriteMessage(b); err != nil {
|
||||||
if len(h.blockTypes) > 0 {
|
return err
|
||||||
for _, blocktype := range h.blockTypes {
|
|
||||||
if blocktype == int32(qType) {
|
|
||||||
b.Release()
|
|
||||||
errors.LogInfo(ctx, "blocked type ", qType, " query for domain ", domain)
|
|
||||||
if h.nonIPQuery == "reject" {
|
|
||||||
err := h.rejectNonIPQuery(id, qType, domain, writer)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if isIPQuery {
|
|
||||||
b.Release()
|
|
||||||
go h.handleIPQuery(id, qType, domain, writer, timer)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if h.nonIPQuery == "drop" {
|
|
||||||
b.Release()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if h.nonIPQuery == "reject" {
|
|
||||||
b.Release()
|
|
||||||
err := h.rejectNonIPQuery(id, qType, domain, writer)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := connWriter.WriteMessage(b); err != nil {
|
id, qType, domain, ok := parseQuery(b.Bytes())
|
||||||
return err
|
if !ok {
|
||||||
|
b.Release()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch h.applyRules(qType, domain) {
|
||||||
|
case RuleAction_Drop:
|
||||||
|
b.Release()
|
||||||
|
errors.LogInfo(ctx, "blocked type ", qType, " query for domain ", domain)
|
||||||
|
case RuleAction_Reject:
|
||||||
|
b.Release()
|
||||||
|
errors.LogInfo(ctx, "rejected type ", qType, " query for domain ", domain)
|
||||||
|
if err := h.rejectNonIPQuery(id, qType, domain, writer); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case RuleAction_Hijack:
|
||||||
|
b.Release()
|
||||||
|
if qType != dnsmessage.TypeA && qType != dnsmessage.TypeAAAA {
|
||||||
|
errors.LogError(ctx, "can only hijack A/AAAA records")
|
||||||
|
if err := h.rejectNonIPQuery(id, qType, domain, writer); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
go h.handleIPQuery(id, qType, domain, writer, timer)
|
||||||
|
}
|
||||||
|
case RuleAction_Direct:
|
||||||
|
if err := connWriter.WriteMessage(b); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic("unknown rule action")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+134
-10
@@ -14,6 +14,7 @@ import (
|
|||||||
_ "github.com/xtls/xray-core/app/proxyman/inbound"
|
_ "github.com/xtls/xray-core/app/proxyman/inbound"
|
||||||
_ "github.com/xtls/xray-core/app/proxyman/outbound"
|
_ "github.com/xtls/xray-core/app/proxyman/outbound"
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/geodata"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/serial"
|
"github.com/xtls/xray-core/common/serial"
|
||||||
"github.com/xtls/xray-core/core"
|
"github.com/xtls/xray-core/core"
|
||||||
@@ -113,9 +114,9 @@ func TestUDPDNSTunnel(t *testing.T) {
|
|||||||
Inbound: []*core.InboundHandlerConfig{
|
Inbound: []*core.InboundHandlerConfig{
|
||||||
{
|
{
|
||||||
ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
|
ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
|
||||||
Address: net.NewIPOrDomain(net.LocalHostIP),
|
RewriteAddress: net.NewIPOrDomain(net.LocalHostIP),
|
||||||
Port: uint32(port),
|
RewritePort: uint32(port),
|
||||||
Networks: []net.Network{net.Network_UDP},
|
AllowedNetworks: []net.Network{net.Network_UDP},
|
||||||
}),
|
}),
|
||||||
ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
|
ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
|
||||||
PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},
|
PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},
|
||||||
@@ -232,9 +233,9 @@ func TestTCPDNSTunnel(t *testing.T) {
|
|||||||
Inbound: []*core.InboundHandlerConfig{
|
Inbound: []*core.InboundHandlerConfig{
|
||||||
{
|
{
|
||||||
ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
|
ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
|
||||||
Address: net.NewIPOrDomain(net.LocalHostIP),
|
RewriteAddress: net.NewIPOrDomain(net.LocalHostIP),
|
||||||
Port: uint32(port),
|
RewritePort: uint32(port),
|
||||||
Networks: []net.Network{net.Network_TCP},
|
AllowedNetworks: []net.Network{net.Network_TCP},
|
||||||
}),
|
}),
|
||||||
ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
|
ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
|
||||||
PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},
|
PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},
|
||||||
@@ -318,9 +319,9 @@ func TestUDP2TCPDNSTunnel(t *testing.T) {
|
|||||||
Inbound: []*core.InboundHandlerConfig{
|
Inbound: []*core.InboundHandlerConfig{
|
||||||
{
|
{
|
||||||
ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
|
ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
|
||||||
Address: net.NewIPOrDomain(net.LocalHostIP),
|
RewriteAddress: net.NewIPOrDomain(net.LocalHostIP),
|
||||||
Port: uint32(port),
|
RewritePort: uint32(port),
|
||||||
Networks: []net.Network{net.Network_TCP},
|
AllowedNetworks: []net.Network{net.Network_TCP},
|
||||||
}),
|
}),
|
||||||
ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
|
ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
|
||||||
PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},
|
PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},
|
||||||
@@ -331,7 +332,7 @@ func TestUDP2TCPDNSTunnel(t *testing.T) {
|
|||||||
Outbound: []*core.OutboundHandlerConfig{
|
Outbound: []*core.OutboundHandlerConfig{
|
||||||
{
|
{
|
||||||
ProxySettings: serial.ToTypedMessage(&dns_proxy.Config{
|
ProxySettings: serial.ToTypedMessage(&dns_proxy.Config{
|
||||||
Server: &net.Endpoint{
|
RewriteServer: &net.Endpoint{
|
||||||
Network: net.Network_TCP,
|
Network: net.Network_TCP,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@@ -368,3 +369,126 @@ func TestUDP2TCPDNSTunnel(t *testing.T) {
|
|||||||
t.Error(r)
|
t.Error(r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDNSRules(t *testing.T) {
|
||||||
|
port := udp.PickPort()
|
||||||
|
|
||||||
|
dnsServer := dns.Server{
|
||||||
|
Addr: "127.0.0.1:" + port.String(),
|
||||||
|
Net: "udp",
|
||||||
|
Handler: &staticHandler{},
|
||||||
|
}
|
||||||
|
defer dnsServer.Shutdown()
|
||||||
|
|
||||||
|
go dnsServer.ListenAndServe()
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
serverPort := udp.PickPort()
|
||||||
|
config := &core.Config{
|
||||||
|
App: []*serial.TypedMessage{
|
||||||
|
serial.ToTypedMessage(&dnsapp.Config{
|
||||||
|
NameServer: []*dnsapp.NameServer{
|
||||||
|
{
|
||||||
|
Address: &net.Endpoint{
|
||||||
|
Network: net.Network_UDP,
|
||||||
|
Address: &net.IPOrDomain{
|
||||||
|
Address: &net.IPOrDomain_Ip{
|
||||||
|
Ip: []byte{127, 0, 0, 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Port: uint32(port),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
serial.ToTypedMessage(&dispatcher.Config{}),
|
||||||
|
serial.ToTypedMessage(&proxyman.OutboundConfig{}),
|
||||||
|
serial.ToTypedMessage(&proxyman.InboundConfig{}),
|
||||||
|
serial.ToTypedMessage(&policy.Config{}),
|
||||||
|
},
|
||||||
|
Inbound: []*core.InboundHandlerConfig{
|
||||||
|
{
|
||||||
|
ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
|
||||||
|
RewriteAddress: net.NewIPOrDomain(net.LocalHostIP),
|
||||||
|
RewritePort: uint32(port),
|
||||||
|
AllowedNetworks: []net.Network{net.Network_UDP},
|
||||||
|
}),
|
||||||
|
ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
|
||||||
|
PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},
|
||||||
|
Listen: net.NewIPOrDomain(net.LocalHostIP),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Outbound: []*core.OutboundHandlerConfig{
|
||||||
|
{
|
||||||
|
ProxySettings: serial.ToTypedMessage(&dns_proxy.Config{
|
||||||
|
Rule: []*dns_proxy.DNSRuleConfig{
|
||||||
|
{
|
||||||
|
Qtype: []int32{int32(dns.TypeA)},
|
||||||
|
Domain: []*geodata.DomainRule{
|
||||||
|
{
|
||||||
|
Value: &geodata.DomainRule_Custom{
|
||||||
|
Custom: &geodata.Domain{
|
||||||
|
Type: geodata.Domain_Domain,
|
||||||
|
Value: "facebook.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: dns_proxy.RuleAction_Direct,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qtype: []int32{int32(dns.TypeA)},
|
||||||
|
Domain: []*geodata.DomainRule{
|
||||||
|
{
|
||||||
|
Value: &geodata.DomainRule_Custom{
|
||||||
|
Custom: &geodata.Domain{
|
||||||
|
Type: geodata.Domain_Full,
|
||||||
|
Value: "google.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: dns_proxy.RuleAction_Reject,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := core.New(config)
|
||||||
|
common.Must(err)
|
||||||
|
common.Must(v.Start())
|
||||||
|
defer v.Close()
|
||||||
|
|
||||||
|
{
|
||||||
|
m1 := new(dns.Msg)
|
||||||
|
m1.Id = dns.Id()
|
||||||
|
m1.RecursionDesired = true
|
||||||
|
m1.Question = []dns.Question{{Name: "google.com.", Qtype: dns.TypeA, Qclass: dns.ClassINET}}
|
||||||
|
|
||||||
|
c := new(dns.Client)
|
||||||
|
in, _, err := c.Exchange(m1, "127.0.0.1:"+strconv.Itoa(int(serverPort)))
|
||||||
|
common.Must(err)
|
||||||
|
|
||||||
|
if in.Rcode != dns.RcodeRefused {
|
||||||
|
t.Fatal("expected Refused, but got ", in.Rcode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
m1 := new(dns.Msg)
|
||||||
|
m1.Id = dns.Id()
|
||||||
|
m1.RecursionDesired = true
|
||||||
|
m1.Question = []dns.Question{{Name: "facebook.com.", Qtype: dns.TypeA, Qclass: dns.ClassINET}}
|
||||||
|
|
||||||
|
c := new(dns.Client)
|
||||||
|
in, _, err := c.Exchange(m1, "127.0.0.1:"+strconv.Itoa(int(serverPort)))
|
||||||
|
common.Must(err)
|
||||||
|
|
||||||
|
if in.Rcode != dns.RcodeSuccess {
|
||||||
|
t.Fatal("expected Success, but got ", in.Rcode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
|
|
||||||
// GetPredefinedAddress returns the defined address from proto config. Null if address is not valid.
|
// GetPredefinedAddress returns the defined address from proto config. Null if address is not valid.
|
||||||
func (v *Config) GetPredefinedAddress() net.Address {
|
func (v *Config) GetPredefinedAddress() net.Address {
|
||||||
addr := v.Address.AsAddress()
|
addr := v.RewriteAddress.AsAddress()
|
||||||
if addr == nil {
|
if addr == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-23
@@ -23,16 +23,16 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Address *net.IPOrDomain `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
|
RewriteAddress *net.IPOrDomain `protobuf:"bytes,1,opt,name=rewrite_address,json=rewriteAddress,proto3" json:"rewrite_address,omitempty"`
|
||||||
Port uint32 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"`
|
RewritePort uint32 `protobuf:"varint,2,opt,name=rewrite_port,json=rewritePort,proto3" json:"rewrite_port,omitempty"`
|
||||||
PortMap map[string]string `protobuf:"bytes,3,rep,name=port_map,json=portMap,proto3" json:"port_map,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
PortMap map[string]string `protobuf:"bytes,3,rep,name=port_map,json=portMap,proto3" json:"port_map,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||||
// List of networks that the Dokodemo accepts.
|
// List of networks that the Dokodemo accepts.
|
||||||
Networks []net.Network `protobuf:"varint,7,rep,packed,name=networks,proto3,enum=xray.common.net.Network" json:"networks,omitempty"`
|
AllowedNetworks []net.Network `protobuf:"varint,7,rep,packed,name=allowed_networks,json=allowedNetworks,proto3,enum=xray.common.net.Network" json:"allowed_networks,omitempty"`
|
||||||
FollowRedirect bool `protobuf:"varint,5,opt,name=follow_redirect,json=followRedirect,proto3" json:"follow_redirect,omitempty"`
|
FollowRedirect bool `protobuf:"varint,5,opt,name=follow_redirect,json=followRedirect,proto3" json:"follow_redirect,omitempty"`
|
||||||
UserLevel uint32 `protobuf:"varint,6,opt,name=user_level,json=userLevel,proto3" json:"user_level,omitempty"`
|
UserLevel uint32 `protobuf:"varint,6,opt,name=user_level,json=userLevel,proto3" json:"user_level,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Config) Reset() {
|
func (x *Config) Reset() {
|
||||||
@@ -65,16 +65,16 @@ func (*Config) Descriptor() ([]byte, []int) {
|
|||||||
return file_proxy_dokodemo_config_proto_rawDescGZIP(), []int{0}
|
return file_proxy_dokodemo_config_proto_rawDescGZIP(), []int{0}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Config) GetAddress() *net.IPOrDomain {
|
func (x *Config) GetRewriteAddress() *net.IPOrDomain {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Address
|
return x.RewriteAddress
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Config) GetPort() uint32 {
|
func (x *Config) GetRewritePort() uint32 {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Port
|
return x.RewritePort
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -86,9 +86,9 @@ func (x *Config) GetPortMap() map[string]string {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Config) GetNetworks() []net.Network {
|
func (x *Config) GetAllowedNetworks() []net.Network {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Networks
|
return x.AllowedNetworks
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -111,12 +111,12 @@ var File_proxy_dokodemo_config_proto protoreflect.FileDescriptor
|
|||||||
|
|
||||||
const file_proxy_dokodemo_config_proto_rawDesc = "" +
|
const file_proxy_dokodemo_config_proto_rawDesc = "" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"\x1bproxy/dokodemo/config.proto\x12\x13xray.proxy.dokodemo\x1a\x18common/net/address.proto\x1a\x18common/net/network.proto\"\xd2\x02\n" +
|
"\x1bproxy/dokodemo/config.proto\x12\x13xray.proxy.dokodemo\x1a\x18common/net/address.proto\x1a\x18common/net/network.proto\"\xff\x02\n" +
|
||||||
"\x06Config\x125\n" +
|
"\x06Config\x12D\n" +
|
||||||
"\aaddress\x18\x01 \x01(\v2\x1b.xray.common.net.IPOrDomainR\aaddress\x12\x12\n" +
|
"\x0frewrite_address\x18\x01 \x01(\v2\x1b.xray.common.net.IPOrDomainR\x0erewriteAddress\x12!\n" +
|
||||||
"\x04port\x18\x02 \x01(\rR\x04port\x12C\n" +
|
"\frewrite_port\x18\x02 \x01(\rR\vrewritePort\x12C\n" +
|
||||||
"\bport_map\x18\x03 \x03(\v2(.xray.proxy.dokodemo.Config.PortMapEntryR\aportMap\x124\n" +
|
"\bport_map\x18\x03 \x03(\v2(.xray.proxy.dokodemo.Config.PortMapEntryR\aportMap\x12C\n" +
|
||||||
"\bnetworks\x18\a \x03(\x0e2\x18.xray.common.net.NetworkR\bnetworks\x12'\n" +
|
"\x10allowed_networks\x18\a \x03(\x0e2\x18.xray.common.net.NetworkR\x0fallowedNetworks\x12'\n" +
|
||||||
"\x0ffollow_redirect\x18\x05 \x01(\bR\x0efollowRedirect\x12\x1d\n" +
|
"\x0ffollow_redirect\x18\x05 \x01(\bR\x0efollowRedirect\x12\x1d\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"user_level\x18\x06 \x01(\rR\tuserLevel\x1a:\n" +
|
"user_level\x18\x06 \x01(\rR\tuserLevel\x1a:\n" +
|
||||||
@@ -145,9 +145,9 @@ var file_proxy_dokodemo_config_proto_goTypes = []any{
|
|||||||
(net.Network)(0), // 3: xray.common.net.Network
|
(net.Network)(0), // 3: xray.common.net.Network
|
||||||
}
|
}
|
||||||
var file_proxy_dokodemo_config_proto_depIdxs = []int32{
|
var file_proxy_dokodemo_config_proto_depIdxs = []int32{
|
||||||
2, // 0: xray.proxy.dokodemo.Config.address:type_name -> xray.common.net.IPOrDomain
|
2, // 0: xray.proxy.dokodemo.Config.rewrite_address:type_name -> xray.common.net.IPOrDomain
|
||||||
1, // 1: xray.proxy.dokodemo.Config.port_map:type_name -> xray.proxy.dokodemo.Config.PortMapEntry
|
1, // 1: xray.proxy.dokodemo.Config.port_map:type_name -> xray.proxy.dokodemo.Config.PortMapEntry
|
||||||
3, // 2: xray.proxy.dokodemo.Config.networks:type_name -> xray.common.net.Network
|
3, // 2: xray.proxy.dokodemo.Config.allowed_networks:type_name -> xray.common.net.Network
|
||||||
3, // [3:3] is the sub-list for method output_type
|
3, // [3:3] is the sub-list for method output_type
|
||||||
3, // [3:3] is the sub-list for method input_type
|
3, // [3:3] is the sub-list for method input_type
|
||||||
3, // [3:3] is the sub-list for extension type_name
|
3, // [3:3] is the sub-list for extension type_name
|
||||||
|
|||||||
@@ -10,14 +10,11 @@ import "common/net/address.proto";
|
|||||||
import "common/net/network.proto";
|
import "common/net/network.proto";
|
||||||
|
|
||||||
message Config {
|
message Config {
|
||||||
xray.common.net.IPOrDomain address = 1;
|
|
||||||
uint32 port = 2;
|
|
||||||
|
|
||||||
map<string, string> port_map = 3;
|
|
||||||
|
|
||||||
// List of networks that the Dokodemo accepts.
|
// List of networks that the Dokodemo accepts.
|
||||||
repeated xray.common.net.Network networks = 7;
|
repeated xray.common.net.Network allowed_networks = 7;
|
||||||
|
xray.common.net.IPOrDomain rewrite_address = 1;
|
||||||
|
uint32 rewrite_port = 2;
|
||||||
|
map<string, string> port_map = 3;
|
||||||
bool follow_redirect = 5;
|
bool follow_redirect = 5;
|
||||||
uint32 user_level = 6;
|
uint32 user_level = 6;
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-15
@@ -32,22 +32,22 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type DokodemoDoor struct {
|
type DokodemoDoor struct {
|
||||||
policyManager policy.Manager
|
policyManager policy.Manager
|
||||||
config *Config
|
config *Config
|
||||||
address net.Address
|
rewriteAddress net.Address
|
||||||
port net.Port
|
rewritePort net.Port
|
||||||
portMap map[string]string
|
portMap map[string]string
|
||||||
sockopt *session.Sockopt
|
sockopt *session.Sockopt
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init initializes the DokodemoDoor instance with necessary parameters.
|
// Init initializes the DokodemoDoor instance with necessary parameters.
|
||||||
func (d *DokodemoDoor) Init(config *Config, pm policy.Manager, sockopt *session.Sockopt) error {
|
func (d *DokodemoDoor) Init(config *Config, pm policy.Manager, sockopt *session.Sockopt) error {
|
||||||
if len(config.Networks) == 0 {
|
if len(config.AllowedNetworks) == 0 {
|
||||||
return errors.New("no network specified")
|
return errors.New("no network specified")
|
||||||
}
|
}
|
||||||
d.config = config
|
d.config = config
|
||||||
d.address = config.GetPredefinedAddress()
|
d.rewriteAddress = config.GetPredefinedAddress()
|
||||||
d.port = net.Port(config.Port)
|
d.rewritePort = net.Port(config.RewritePort)
|
||||||
d.portMap = config.PortMap
|
d.portMap = config.PortMap
|
||||||
d.policyManager = pm
|
d.policyManager = pm
|
||||||
d.sockopt = sockopt
|
d.sockopt = sockopt
|
||||||
@@ -57,10 +57,10 @@ func (d *DokodemoDoor) Init(config *Config, pm policy.Manager, sockopt *session.
|
|||||||
|
|
||||||
// Network implements proxy.Inbound.
|
// Network implements proxy.Inbound.
|
||||||
func (d *DokodemoDoor) Network() []net.Network {
|
func (d *DokodemoDoor) Network() []net.Network {
|
||||||
if slices.Contains(d.config.Networks, net.Network_TCP) {
|
if slices.Contains(d.config.AllowedNetworks, net.Network_TCP) {
|
||||||
return append(d.config.Networks, net.Network_UNIX)
|
return append(d.config.AllowedNetworks, net.Network_UNIX)
|
||||||
}
|
}
|
||||||
return d.config.Networks
|
return d.config.AllowedNetworks
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DokodemoDoor) policy() policy.Session {
|
func (d *DokodemoDoor) policy() policy.Session {
|
||||||
@@ -78,8 +78,8 @@ func (d *DokodemoDoor) Process(ctx context.Context, network net.Network, conn st
|
|||||||
}
|
}
|
||||||
dest := net.Destination{
|
dest := net.Destination{
|
||||||
Network: network,
|
Network: network,
|
||||||
Address: d.address,
|
Address: d.rewriteAddress,
|
||||||
Port: d.port,
|
Port: d.rewritePort,
|
||||||
}
|
}
|
||||||
|
|
||||||
if !d.config.FollowRedirect {
|
if !d.config.FollowRedirect {
|
||||||
@@ -95,7 +95,7 @@ func (d *DokodemoDoor) Process(ctx context.Context, network net.Network, conn st
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if dest.Port == 0 {
|
if dest.Port == 0 && port != "" {
|
||||||
dest.Port = net.Port(common.Must2(strconv.Atoi(port)))
|
dest.Port = net.Port(common.Must2(strconv.Atoi(port)))
|
||||||
}
|
}
|
||||||
if d.portMap != nil && d.portMap[port] != "" {
|
if d.portMap != nil && d.portMap[port] != "" {
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
package freedom
|
|
||||||
+200
-49
@@ -8,6 +8,7 @@ package freedom
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
geodata "github.com/xtls/xray-core/common/geodata"
|
geodata "github.com/xtls/xray-core/common/geodata"
|
||||||
|
net "github.com/xtls/xray-core/common/net"
|
||||||
protocol "github.com/xtls/xray-core/common/protocol"
|
protocol "github.com/xtls/xray-core/common/protocol"
|
||||||
internet "github.com/xtls/xray-core/transport/internet"
|
internet "github.com/xtls/xray-core/transport/internet"
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
@@ -24,6 +25,52 @@ const (
|
|||||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type RuleAction int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
RuleAction_Allow RuleAction = 0
|
||||||
|
RuleAction_Block RuleAction = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
// Enum value maps for RuleAction.
|
||||||
|
var (
|
||||||
|
RuleAction_name = map[int32]string{
|
||||||
|
0: "Allow",
|
||||||
|
1: "Block",
|
||||||
|
}
|
||||||
|
RuleAction_value = map[string]int32{
|
||||||
|
"Allow": 0,
|
||||||
|
"Block": 1,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (x RuleAction) Enum() *RuleAction {
|
||||||
|
p := new(RuleAction)
|
||||||
|
*p = x
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x RuleAction) String() string {
|
||||||
|
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (RuleAction) Descriptor() protoreflect.EnumDescriptor {
|
||||||
|
return file_proxy_freedom_config_proto_enumTypes[0].Descriptor()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (RuleAction) Type() protoreflect.EnumType {
|
||||||
|
return &file_proxy_freedom_config_proto_enumTypes[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x RuleAction) Number() protoreflect.EnumNumber {
|
||||||
|
return protoreflect.EnumNumber(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use RuleAction.Descriptor instead.
|
||||||
|
func (RuleAction) EnumDescriptor() ([]byte, []int) {
|
||||||
|
return file_proxy_freedom_config_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
type DestinationOverride struct {
|
type DestinationOverride struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Server *protocol.ServerEndpoint `protobuf:"bytes,1,opt,name=server,proto3" json:"server,omitempty"`
|
Server *protocol.ServerEndpoint `protobuf:"bytes,1,opt,name=server,proto3" json:"server,omitempty"`
|
||||||
@@ -252,27 +299,28 @@ func (x *Noise) GetApplyTo() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
type IPRules struct {
|
type Range struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Rules []*geodata.IPRule `protobuf:"bytes,1,rep,name=rules,proto3" json:"rules,omitempty"`
|
Min uint64 `protobuf:"varint,1,opt,name=min,proto3" json:"min,omitempty"`
|
||||||
|
Max uint64 `protobuf:"varint,2,opt,name=max,proto3" json:"max,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *IPRules) Reset() {
|
func (x *Range) Reset() {
|
||||||
*x = IPRules{}
|
*x = Range{}
|
||||||
mi := &file_proxy_freedom_config_proto_msgTypes[3]
|
mi := &file_proxy_freedom_config_proto_msgTypes[3]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *IPRules) String() string {
|
func (x *Range) String() string {
|
||||||
return protoimpl.X.MessageStringOf(x)
|
return protoimpl.X.MessageStringOf(x)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*IPRules) ProtoMessage() {}
|
func (*Range) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *IPRules) ProtoReflect() protoreflect.Message {
|
func (x *Range) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_proxy_freedom_config_proto_msgTypes[3]
|
mi := &file_proxy_freedom_config_proto_msgTypes[3]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
@@ -284,14 +332,97 @@ func (x *IPRules) ProtoReflect() protoreflect.Message {
|
|||||||
return mi.MessageOf(x)
|
return mi.MessageOf(x)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deprecated: Use IPRules.ProtoReflect.Descriptor instead.
|
// Deprecated: Use Range.ProtoReflect.Descriptor instead.
|
||||||
func (*IPRules) Descriptor() ([]byte, []int) {
|
func (*Range) Descriptor() ([]byte, []int) {
|
||||||
return file_proxy_freedom_config_proto_rawDescGZIP(), []int{3}
|
return file_proxy_freedom_config_proto_rawDescGZIP(), []int{3}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *IPRules) GetRules() []*geodata.IPRule {
|
func (x *Range) GetMin() uint64 {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Rules
|
return x.Min
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Range) GetMax() uint64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Max
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type FinalRuleConfig struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Action RuleAction `protobuf:"varint,1,opt,name=action,proto3,enum=xray.proxy.freedom.RuleAction" json:"action,omitempty"`
|
||||||
|
Networks []net.Network `protobuf:"varint,2,rep,packed,name=networks,proto3,enum=xray.common.net.Network" json:"networks,omitempty"`
|
||||||
|
PortList *net.PortList `protobuf:"bytes,3,opt,name=port_list,json=portList,proto3" json:"port_list,omitempty"`
|
||||||
|
Ip []*geodata.IPRule `protobuf:"bytes,4,rep,name=ip,proto3" json:"ip,omitempty"`
|
||||||
|
BlockDelay *Range `protobuf:"bytes,5,opt,name=block_delay,json=blockDelay,proto3" json:"block_delay,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *FinalRuleConfig) Reset() {
|
||||||
|
*x = FinalRuleConfig{}
|
||||||
|
mi := &file_proxy_freedom_config_proto_msgTypes[4]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *FinalRuleConfig) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*FinalRuleConfig) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *FinalRuleConfig) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_proxy_freedom_config_proto_msgTypes[4]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use FinalRuleConfig.ProtoReflect.Descriptor instead.
|
||||||
|
func (*FinalRuleConfig) Descriptor() ([]byte, []int) {
|
||||||
|
return file_proxy_freedom_config_proto_rawDescGZIP(), []int{4}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *FinalRuleConfig) GetAction() RuleAction {
|
||||||
|
if x != nil {
|
||||||
|
return x.Action
|
||||||
|
}
|
||||||
|
return RuleAction_Allow
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *FinalRuleConfig) GetNetworks() []net.Network {
|
||||||
|
if x != nil {
|
||||||
|
return x.Networks
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *FinalRuleConfig) GetPortList() *net.PortList {
|
||||||
|
if x != nil {
|
||||||
|
return x.PortList
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *FinalRuleConfig) GetIp() []*geodata.IPRule {
|
||||||
|
if x != nil {
|
||||||
|
return x.Ip
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *FinalRuleConfig) GetBlockDelay() *Range {
|
||||||
|
if x != nil {
|
||||||
|
return x.BlockDelay
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -304,14 +435,14 @@ type Config struct {
|
|||||||
Fragment *Fragment `protobuf:"bytes,5,opt,name=fragment,proto3" json:"fragment,omitempty"`
|
Fragment *Fragment `protobuf:"bytes,5,opt,name=fragment,proto3" json:"fragment,omitempty"`
|
||||||
ProxyProtocol uint32 `protobuf:"varint,6,opt,name=proxy_protocol,json=proxyProtocol,proto3" json:"proxy_protocol,omitempty"`
|
ProxyProtocol uint32 `protobuf:"varint,6,opt,name=proxy_protocol,json=proxyProtocol,proto3" json:"proxy_protocol,omitempty"`
|
||||||
Noises []*Noise `protobuf:"bytes,7,rep,name=noises,proto3" json:"noises,omitempty"`
|
Noises []*Noise `protobuf:"bytes,7,rep,name=noises,proto3" json:"noises,omitempty"`
|
||||||
IpsBlocked *IPRules `protobuf:"bytes,8,opt,name=ips_blocked,json=ipsBlocked,proto3,oneof" json:"ips_blocked,omitempty"`
|
FinalRules []*FinalRuleConfig `protobuf:"bytes,8,rep,name=final_rules,json=finalRules,proto3" json:"final_rules,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Config) Reset() {
|
func (x *Config) Reset() {
|
||||||
*x = Config{}
|
*x = Config{}
|
||||||
mi := &file_proxy_freedom_config_proto_msgTypes[4]
|
mi := &file_proxy_freedom_config_proto_msgTypes[5]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -323,7 +454,7 @@ func (x *Config) String() string {
|
|||||||
func (*Config) ProtoMessage() {}
|
func (*Config) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *Config) ProtoReflect() protoreflect.Message {
|
func (x *Config) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_proxy_freedom_config_proto_msgTypes[4]
|
mi := &file_proxy_freedom_config_proto_msgTypes[5]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -336,7 +467,7 @@ func (x *Config) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
|
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
|
||||||
func (*Config) Descriptor() ([]byte, []int) {
|
func (*Config) Descriptor() ([]byte, []int) {
|
||||||
return file_proxy_freedom_config_proto_rawDescGZIP(), []int{4}
|
return file_proxy_freedom_config_proto_rawDescGZIP(), []int{5}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Config) GetDomainStrategy() internet.DomainStrategy {
|
func (x *Config) GetDomainStrategy() internet.DomainStrategy {
|
||||||
@@ -381,9 +512,9 @@ func (x *Config) GetNoises() []*Noise {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Config) GetIpsBlocked() *IPRules {
|
func (x *Config) GetFinalRules() []*FinalRuleConfig {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.IpsBlocked
|
return x.FinalRules
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -392,7 +523,7 @@ var File_proxy_freedom_config_proto protoreflect.FileDescriptor
|
|||||||
|
|
||||||
const file_proxy_freedom_config_proto_rawDesc = "" +
|
const file_proxy_freedom_config_proto_rawDesc = "" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"\x1aproxy/freedom/config.proto\x12\x12xray.proxy.freedom\x1a!common/protocol/server_spec.proto\x1a\x1ftransport/internet/config.proto\x1a\x1bcommon/geodata/geodat.proto\"S\n" +
|
"\x1aproxy/freedom/config.proto\x12\x12xray.proxy.freedom\x1a!common/protocol/server_spec.proto\x1a\x1ftransport/internet/config.proto\x1a\x15common/net/port.proto\x1a\x18common/net/network.proto\x1a\x1bcommon/geodata/geodat.proto\"S\n" +
|
||||||
"\x13DestinationOverride\x12<\n" +
|
"\x13DestinationOverride\x12<\n" +
|
||||||
"\x06server\x18\x01 \x01(\v2$.xray.common.protocol.ServerEndpointR\x06server\"\x98\x02\n" +
|
"\x06server\x18\x01 \x01(\v2$.xray.common.protocol.ServerEndpointR\x06server\"\x98\x02\n" +
|
||||||
"\bFragment\x12!\n" +
|
"\bFragment\x12!\n" +
|
||||||
@@ -415,9 +546,17 @@ const file_proxy_freedom_config_proto_rawDesc = "" +
|
|||||||
"\tdelay_min\x18\x03 \x01(\x04R\bdelayMin\x12\x1b\n" +
|
"\tdelay_min\x18\x03 \x01(\x04R\bdelayMin\x12\x1b\n" +
|
||||||
"\tdelay_max\x18\x04 \x01(\x04R\bdelayMax\x12\x16\n" +
|
"\tdelay_max\x18\x04 \x01(\x04R\bdelayMax\x12\x16\n" +
|
||||||
"\x06packet\x18\x05 \x01(\fR\x06packet\x12\x19\n" +
|
"\x06packet\x18\x05 \x01(\fR\x06packet\x12\x19\n" +
|
||||||
"\bapply_to\x18\x06 \x01(\tR\aapplyTo\"<\n" +
|
"\bapply_to\x18\x06 \x01(\tR\aapplyTo\"+\n" +
|
||||||
"\aIPRules\x121\n" +
|
"\x05Range\x12\x10\n" +
|
||||||
"\x05rules\x18\x01 \x03(\v2\x1b.xray.common.geodata.IPRuleR\x05rules\"\xbc\x03\n" +
|
"\x03min\x18\x01 \x01(\x04R\x03min\x12\x10\n" +
|
||||||
|
"\x03max\x18\x02 \x01(\x04R\x03max\"\xa0\x02\n" +
|
||||||
|
"\x0fFinalRuleConfig\x126\n" +
|
||||||
|
"\x06action\x18\x01 \x01(\x0e2\x1e.xray.proxy.freedom.RuleActionR\x06action\x124\n" +
|
||||||
|
"\bnetworks\x18\x02 \x03(\x0e2\x18.xray.common.net.NetworkR\bnetworks\x126\n" +
|
||||||
|
"\tport_list\x18\x03 \x01(\v2\x19.xray.common.net.PortListR\bportList\x12+\n" +
|
||||||
|
"\x02ip\x18\x04 \x03(\v2\x1b.xray.common.geodata.IPRuleR\x02ip\x12:\n" +
|
||||||
|
"\vblock_delay\x18\x05 \x01(\v2\x19.xray.proxy.freedom.RangeR\n" +
|
||||||
|
"blockDelay\"\xaf\x03\n" +
|
||||||
"\x06Config\x12P\n" +
|
"\x06Config\x12P\n" +
|
||||||
"\x0fdomain_strategy\x18\x01 \x01(\x0e2'.xray.transport.internet.DomainStrategyR\x0edomainStrategy\x12Z\n" +
|
"\x0fdomain_strategy\x18\x01 \x01(\x0e2'.xray.transport.internet.DomainStrategyR\x0edomainStrategy\x12Z\n" +
|
||||||
"\x14destination_override\x18\x03 \x01(\v2'.xray.proxy.freedom.DestinationOverrideR\x13destinationOverride\x12\x1d\n" +
|
"\x14destination_override\x18\x03 \x01(\v2'.xray.proxy.freedom.DestinationOverrideR\x13destinationOverride\x12\x1d\n" +
|
||||||
@@ -425,10 +564,13 @@ const file_proxy_freedom_config_proto_rawDesc = "" +
|
|||||||
"user_level\x18\x04 \x01(\rR\tuserLevel\x128\n" +
|
"user_level\x18\x04 \x01(\rR\tuserLevel\x128\n" +
|
||||||
"\bfragment\x18\x05 \x01(\v2\x1c.xray.proxy.freedom.FragmentR\bfragment\x12%\n" +
|
"\bfragment\x18\x05 \x01(\v2\x1c.xray.proxy.freedom.FragmentR\bfragment\x12%\n" +
|
||||||
"\x0eproxy_protocol\x18\x06 \x01(\rR\rproxyProtocol\x121\n" +
|
"\x0eproxy_protocol\x18\x06 \x01(\rR\rproxyProtocol\x121\n" +
|
||||||
"\x06noises\x18\a \x03(\v2\x19.xray.proxy.freedom.NoiseR\x06noises\x12A\n" +
|
"\x06noises\x18\a \x03(\v2\x19.xray.proxy.freedom.NoiseR\x06noises\x12D\n" +
|
||||||
"\vips_blocked\x18\b \x01(\v2\x1b.xray.proxy.freedom.IPRulesH\x00R\n" +
|
"\vfinal_rules\x18\b \x03(\v2#.xray.proxy.freedom.FinalRuleConfigR\n" +
|
||||||
"ipsBlocked\x88\x01\x01B\x0e\n" +
|
"finalRules*\"\n" +
|
||||||
"\f_ips_blockedBX\n" +
|
"\n" +
|
||||||
|
"RuleAction\x12\t\n" +
|
||||||
|
"\x05Allow\x10\x00\x12\t\n" +
|
||||||
|
"\x05Block\x10\x01BX\n" +
|
||||||
"\x16com.xray.proxy.freedomP\x01Z'github.com/xtls/xray-core/proxy/freedom\xaa\x02\x12Xray.Proxy.Freedomb\x06proto3"
|
"\x16com.xray.proxy.freedomP\x01Z'github.com/xtls/xray-core/proxy/freedom\xaa\x02\x12Xray.Proxy.Freedomb\x06proto3"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -443,30 +585,39 @@ func file_proxy_freedom_config_proto_rawDescGZIP() []byte {
|
|||||||
return file_proxy_freedom_config_proto_rawDescData
|
return file_proxy_freedom_config_proto_rawDescData
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_proxy_freedom_config_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
|
var file_proxy_freedom_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||||
|
var file_proxy_freedom_config_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
|
||||||
var file_proxy_freedom_config_proto_goTypes = []any{
|
var file_proxy_freedom_config_proto_goTypes = []any{
|
||||||
(*DestinationOverride)(nil), // 0: xray.proxy.freedom.DestinationOverride
|
(RuleAction)(0), // 0: xray.proxy.freedom.RuleAction
|
||||||
(*Fragment)(nil), // 1: xray.proxy.freedom.Fragment
|
(*DestinationOverride)(nil), // 1: xray.proxy.freedom.DestinationOverride
|
||||||
(*Noise)(nil), // 2: xray.proxy.freedom.Noise
|
(*Fragment)(nil), // 2: xray.proxy.freedom.Fragment
|
||||||
(*IPRules)(nil), // 3: xray.proxy.freedom.IPRules
|
(*Noise)(nil), // 3: xray.proxy.freedom.Noise
|
||||||
(*Config)(nil), // 4: xray.proxy.freedom.Config
|
(*Range)(nil), // 4: xray.proxy.freedom.Range
|
||||||
(*protocol.ServerEndpoint)(nil), // 5: xray.common.protocol.ServerEndpoint
|
(*FinalRuleConfig)(nil), // 5: xray.proxy.freedom.FinalRuleConfig
|
||||||
(*geodata.IPRule)(nil), // 6: xray.common.geodata.IPRule
|
(*Config)(nil), // 6: xray.proxy.freedom.Config
|
||||||
(internet.DomainStrategy)(0), // 7: xray.transport.internet.DomainStrategy
|
(*protocol.ServerEndpoint)(nil), // 7: xray.common.protocol.ServerEndpoint
|
||||||
|
(net.Network)(0), // 8: xray.common.net.Network
|
||||||
|
(*net.PortList)(nil), // 9: xray.common.net.PortList
|
||||||
|
(*geodata.IPRule)(nil), // 10: xray.common.geodata.IPRule
|
||||||
|
(internet.DomainStrategy)(0), // 11: xray.transport.internet.DomainStrategy
|
||||||
}
|
}
|
||||||
var file_proxy_freedom_config_proto_depIdxs = []int32{
|
var file_proxy_freedom_config_proto_depIdxs = []int32{
|
||||||
5, // 0: xray.proxy.freedom.DestinationOverride.server:type_name -> xray.common.protocol.ServerEndpoint
|
7, // 0: xray.proxy.freedom.DestinationOverride.server:type_name -> xray.common.protocol.ServerEndpoint
|
||||||
6, // 1: xray.proxy.freedom.IPRules.rules:type_name -> xray.common.geodata.IPRule
|
0, // 1: xray.proxy.freedom.FinalRuleConfig.action:type_name -> xray.proxy.freedom.RuleAction
|
||||||
7, // 2: xray.proxy.freedom.Config.domain_strategy:type_name -> xray.transport.internet.DomainStrategy
|
8, // 2: xray.proxy.freedom.FinalRuleConfig.networks:type_name -> xray.common.net.Network
|
||||||
0, // 3: xray.proxy.freedom.Config.destination_override:type_name -> xray.proxy.freedom.DestinationOverride
|
9, // 3: xray.proxy.freedom.FinalRuleConfig.port_list:type_name -> xray.common.net.PortList
|
||||||
1, // 4: xray.proxy.freedom.Config.fragment:type_name -> xray.proxy.freedom.Fragment
|
10, // 4: xray.proxy.freedom.FinalRuleConfig.ip:type_name -> xray.common.geodata.IPRule
|
||||||
2, // 5: xray.proxy.freedom.Config.noises:type_name -> xray.proxy.freedom.Noise
|
4, // 5: xray.proxy.freedom.FinalRuleConfig.block_delay:type_name -> xray.proxy.freedom.Range
|
||||||
3, // 6: xray.proxy.freedom.Config.ips_blocked:type_name -> xray.proxy.freedom.IPRules
|
11, // 6: xray.proxy.freedom.Config.domain_strategy:type_name -> xray.transport.internet.DomainStrategy
|
||||||
7, // [7:7] is the sub-list for method output_type
|
1, // 7: xray.proxy.freedom.Config.destination_override:type_name -> xray.proxy.freedom.DestinationOverride
|
||||||
7, // [7:7] is the sub-list for method input_type
|
2, // 8: xray.proxy.freedom.Config.fragment:type_name -> xray.proxy.freedom.Fragment
|
||||||
7, // [7:7] is the sub-list for extension type_name
|
3, // 9: xray.proxy.freedom.Config.noises:type_name -> xray.proxy.freedom.Noise
|
||||||
7, // [7:7] is the sub-list for extension extendee
|
5, // 10: xray.proxy.freedom.Config.final_rules:type_name -> xray.proxy.freedom.FinalRuleConfig
|
||||||
0, // [0:7] is the sub-list for field type_name
|
11, // [11:11] is the sub-list for method output_type
|
||||||
|
11, // [11:11] is the sub-list for method input_type
|
||||||
|
11, // [11:11] is the sub-list for extension type_name
|
||||||
|
11, // [11:11] is the sub-list for extension extendee
|
||||||
|
0, // [0:11] is the sub-list for field type_name
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { file_proxy_freedom_config_proto_init() }
|
func init() { file_proxy_freedom_config_proto_init() }
|
||||||
@@ -474,19 +625,19 @@ func file_proxy_freedom_config_proto_init() {
|
|||||||
if File_proxy_freedom_config_proto != nil {
|
if File_proxy_freedom_config_proto != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
file_proxy_freedom_config_proto_msgTypes[4].OneofWrappers = []any{}
|
|
||||||
type x struct{}
|
type x struct{}
|
||||||
out := protoimpl.TypeBuilder{
|
out := protoimpl.TypeBuilder{
|
||||||
File: protoimpl.DescBuilder{
|
File: protoimpl.DescBuilder{
|
||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_freedom_config_proto_rawDesc), len(file_proxy_freedom_config_proto_rawDesc)),
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_freedom_config_proto_rawDesc), len(file_proxy_freedom_config_proto_rawDesc)),
|
||||||
NumEnums: 0,
|
NumEnums: 1,
|
||||||
NumMessages: 5,
|
NumMessages: 6,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 0,
|
NumServices: 0,
|
||||||
},
|
},
|
||||||
GoTypes: file_proxy_freedom_config_proto_goTypes,
|
GoTypes: file_proxy_freedom_config_proto_goTypes,
|
||||||
DependencyIndexes: file_proxy_freedom_config_proto_depIdxs,
|
DependencyIndexes: file_proxy_freedom_config_proto_depIdxs,
|
||||||
|
EnumInfos: file_proxy_freedom_config_proto_enumTypes,
|
||||||
MessageInfos: file_proxy_freedom_config_proto_msgTypes,
|
MessageInfos: file_proxy_freedom_config_proto_msgTypes,
|
||||||
}.Build()
|
}.Build()
|
||||||
File_proxy_freedom_config_proto = out.File
|
File_proxy_freedom_config_proto = out.File
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ option java_multiple_files = true;
|
|||||||
|
|
||||||
import "common/protocol/server_spec.proto";
|
import "common/protocol/server_spec.proto";
|
||||||
import "transport/internet/config.proto";
|
import "transport/internet/config.proto";
|
||||||
|
import "common/net/port.proto";
|
||||||
|
import "common/net/network.proto";
|
||||||
import "common/geodata/geodat.proto";
|
import "common/geodata/geodat.proto";
|
||||||
|
|
||||||
message DestinationOverride {
|
message DestinationOverride {
|
||||||
@@ -24,6 +26,7 @@ message Fragment {
|
|||||||
uint64 max_split_min = 7;
|
uint64 max_split_min = 7;
|
||||||
uint64 max_split_max = 8;
|
uint64 max_split_max = 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Noise {
|
message Noise {
|
||||||
uint64 length_min = 1;
|
uint64 length_min = 1;
|
||||||
uint64 length_max = 2;
|
uint64 length_max = 2;
|
||||||
@@ -33,8 +36,22 @@ message Noise {
|
|||||||
string apply_to = 6;
|
string apply_to = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
message IPRules {
|
message Range {
|
||||||
repeated xray.common.geodata.IPRule rules = 1;
|
uint64 min = 1;
|
||||||
|
uint64 max = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum RuleAction {
|
||||||
|
Allow = 0;
|
||||||
|
Block = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message FinalRuleConfig {
|
||||||
|
RuleAction action = 1;
|
||||||
|
repeated xray.common.net.Network networks = 2;
|
||||||
|
xray.common.net.PortList port_list = 3;
|
||||||
|
repeated xray.common.geodata.IPRule ip = 4;
|
||||||
|
Range block_delay = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Config {
|
message Config {
|
||||||
@@ -44,5 +61,5 @@ message Config {
|
|||||||
Fragment fragment = 5;
|
Fragment fragment = 5;
|
||||||
uint32 proxy_protocol = 6;
|
uint32 proxy_protocol = 6;
|
||||||
repeated Noise noises = 7;
|
repeated Noise noises = 7;
|
||||||
optional IPRules ips_blocked = 8;
|
repeated FinalRuleConfig final_rules = 8;
|
||||||
}
|
}
|
||||||
|
|||||||
+241
-78
@@ -31,32 +31,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var useSplice bool
|
var useSplice bool
|
||||||
|
var allNetworks [8]bool
|
||||||
var defaultPrivateBlockIP = []string{
|
var defaultBlockPrivateRule *FinalRule
|
||||||
"0.0.0.0/8",
|
var defaultBlockAllRule *FinalRule
|
||||||
"10.0.0.0/8",
|
|
||||||
"100.64.0.0/10",
|
|
||||||
"127.0.0.0/8",
|
|
||||||
"169.254.0.0/16",
|
|
||||||
"172.16.0.0/12",
|
|
||||||
"192.0.0.0/24",
|
|
||||||
"192.0.2.0/24",
|
|
||||||
"192.88.99.0/24",
|
|
||||||
"192.168.0.0/16",
|
|
||||||
"198.18.0.0/15",
|
|
||||||
"198.51.100.0/24",
|
|
||||||
"203.0.113.0/24",
|
|
||||||
"224.0.0.0/3",
|
|
||||||
"::/127",
|
|
||||||
"fc00::/7",
|
|
||||||
"fe80::/10",
|
|
||||||
"ff00::/8",
|
|
||||||
}
|
|
||||||
|
|
||||||
var defaultPrivateBlockIPMatcher = func() geodata.IPMatcher {
|
|
||||||
rules := common.Must2(geodata.ParseIPRules(defaultPrivateBlockIP))
|
|
||||||
return common.Must2(geodata.IPReg.BuildIPMatcher(rules))
|
|
||||||
}()
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
||||||
@@ -68,31 +45,184 @@ func init() {
|
|||||||
}
|
}
|
||||||
return h, nil
|
return h, nil
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const defaultFlagValue = "NOT_DEFINED_AT_ALL"
|
const defaultFlagValue = "NOT_DEFINED_AT_ALL"
|
||||||
value := platform.NewEnvFlag(platform.UseFreedomSplice).GetValue(func() string { return defaultFlagValue })
|
value := platform.NewEnvFlag(platform.UseFreedomSplice).GetValue(func() string { return defaultFlagValue })
|
||||||
switch value {
|
switch value {
|
||||||
case defaultFlagValue, "auto", "enable":
|
case defaultFlagValue, "auto", "enable":
|
||||||
useSplice = true
|
useSplice = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for i := range allNetworks {
|
||||||
|
allNetworks[i] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultBlockPrivateRule = &FinalRule{
|
||||||
|
action: RuleAction_Block,
|
||||||
|
network: allNetworks,
|
||||||
|
ip: common.Must2(geodata.IPReg.BuildIPMatcher(common.Must2(geodata.ParseIPRules([]string{
|
||||||
|
"0.0.0.0/8",
|
||||||
|
"10.0.0.0/8",
|
||||||
|
"100.64.0.0/10",
|
||||||
|
"127.0.0.0/8",
|
||||||
|
"169.254.0.0/16",
|
||||||
|
"172.16.0.0/12",
|
||||||
|
"192.0.0.0/24",
|
||||||
|
"192.0.2.0/24",
|
||||||
|
"192.88.99.0/24",
|
||||||
|
"192.168.0.0/16",
|
||||||
|
"198.18.0.0/15",
|
||||||
|
"198.51.100.0/24",
|
||||||
|
"203.0.113.0/24",
|
||||||
|
"224.0.0.0/3",
|
||||||
|
"::/127",
|
||||||
|
"fc00::/7",
|
||||||
|
"fe80::/10",
|
||||||
|
"ff00::/8",
|
||||||
|
})))),
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultBlockAllRule = &FinalRule{
|
||||||
|
action: RuleAction_Block,
|
||||||
|
network: allNetworks,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type FinalRule struct {
|
||||||
|
action RuleAction
|
||||||
|
network [8]bool
|
||||||
|
port net.MemoryPortList
|
||||||
|
ip geodata.IPMatcher
|
||||||
|
blockDelay *Range
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handler handles Freedom connections.
|
// Handler handles Freedom connections.
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
policyManager policy.Manager
|
policyManager policy.Manager
|
||||||
config *Config
|
config *Config
|
||||||
blockedIPMatcher geodata.IPMatcher
|
finalRules []*FinalRule
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildFinalRule(config *FinalRuleConfig) (*FinalRule, error) {
|
||||||
|
rule := &FinalRule{
|
||||||
|
action: config.GetAction(),
|
||||||
|
blockDelay: config.GetBlockDelay(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(config.Networks) == 0 {
|
||||||
|
rule.network = allNetworks
|
||||||
|
} else {
|
||||||
|
for _, network := range config.Networks {
|
||||||
|
rule.network[int(network)] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.PortList != nil {
|
||||||
|
rule.port = net.PortListFromProto(config.PortList)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(config.Ip) > 0 {
|
||||||
|
matcher, err := geodata.IPReg.BuildIPMatcher(config.Ip)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rule.ip = matcher
|
||||||
|
}
|
||||||
|
|
||||||
|
return rule, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FinalRule) matchNetwork(network net.Network) bool {
|
||||||
|
return r.network[int(network)]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FinalRule) matchPort(port net.Port) bool {
|
||||||
|
if len(r.port) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return r.port.Contains(port)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FinalRule) matchIP(addr net.Address) bool {
|
||||||
|
if r.ip == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return addr != nil && addr.Family().IsIP() && r.ip.Match(addr.IP())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FinalRule) Apply(network net.Network, address net.Address, port net.Port) bool {
|
||||||
|
if !r.matchNetwork(network) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !r.matchPort(port) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return r.matchIP(address)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDefaultFinalRule(inbound *session.Inbound) *FinalRule {
|
||||||
|
if inbound == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
switch inbound.Name {
|
||||||
|
case "vless-reverse":
|
||||||
|
return defaultBlockAllRule
|
||||||
|
case "vless", "vmess", "trojan", "hysteria", "wireguard":
|
||||||
|
return defaultBlockPrivateRule
|
||||||
|
default:
|
||||||
|
if strings.HasPrefix(inbound.Name, "shadowsocks") {
|
||||||
|
return defaultBlockPrivateRule
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) shouldResolveDomainBeforeFinalRules(dialDest net.Destination, defaultRule *FinalRule) bool {
|
||||||
|
if !dialDest.Address.Family().IsDomain() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if len(h.finalRules) > 0 {
|
||||||
|
rule := h.finalRules[0]
|
||||||
|
if rule.action == RuleAction_Allow && rule.network[dialDest.Network] && len(rule.port) == 0 && rule.ip == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if defaultRule != nil || len(h.finalRules) > 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) matchFinalRule(network net.Network, address net.Address, port net.Port, defaultRule *FinalRule) *FinalRule {
|
||||||
|
for _, rule := range h.finalRules {
|
||||||
|
if rule.Apply(network, address, port) {
|
||||||
|
return rule
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if defaultRule != nil && defaultRule.Apply(network, address, port) {
|
||||||
|
return defaultRule
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) applyFinalRules(network net.Network, address net.Address, port net.Port, defaultRule *FinalRule) RuleAction {
|
||||||
|
if rule := h.matchFinalRule(network, address, port, defaultRule); rule != nil {
|
||||||
|
return rule.action
|
||||||
|
}
|
||||||
|
return RuleAction_Allow
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init initializes the Handler with necessary parameters.
|
// Init initializes the Handler with necessary parameters.
|
||||||
func (h *Handler) Init(config *Config, pm policy.Manager) error {
|
func (h *Handler) Init(config *Config, pm policy.Manager) error {
|
||||||
h.config = config
|
h.config = config
|
||||||
h.policyManager = pm
|
h.policyManager = pm
|
||||||
if config.IpsBlocked != nil && len(config.IpsBlocked.Rules) > 0 {
|
h.finalRules = make([]*FinalRule, 0, len(config.FinalRules))
|
||||||
m, err := geodata.IPReg.BuildIPMatcher(config.IpsBlocked.Rules)
|
for _, rc := range config.FinalRules {
|
||||||
|
rule, err := buildFinalRule(rc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("failed to build blocked ip matcher").Base(err)
|
return errors.New("failed to build final rule").Base(err)
|
||||||
}
|
}
|
||||||
h.blockedIPMatcher = m
|
h.finalRules = append(h.finalRules, rule)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -102,6 +232,20 @@ func (h *Handler) policy() policy.Session {
|
|||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Handler) blockDelay(rule *FinalRule) time.Duration {
|
||||||
|
min := uint64(30)
|
||||||
|
max := uint64(90)
|
||||||
|
if rule.blockDelay != nil {
|
||||||
|
min = rule.blockDelay.Min
|
||||||
|
max = rule.blockDelay.Max
|
||||||
|
}
|
||||||
|
span := max - min
|
||||||
|
if max < min {
|
||||||
|
span = min - max
|
||||||
|
}
|
||||||
|
return time.Duration(min+uint64(dice.Roll(int(span+1)))) * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
func isValidAddress(addr *net.IPOrDomain) bool {
|
func isValidAddress(addr *net.IPOrDomain) bool {
|
||||||
if addr == nil {
|
if addr == nil {
|
||||||
return false
|
return false
|
||||||
@@ -111,32 +255,6 @@ func isValidAddress(addr *net.IPOrDomain) bool {
|
|||||||
return a != net.AnyIP && a != net.AnyIPv6
|
return a != net.AnyIP && a != net.AnyIPv6
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) getBlockedIPMatcher(ctx context.Context, inbound *session.Inbound) geodata.IPMatcher {
|
|
||||||
if h.blockedIPMatcher != nil {
|
|
||||||
return h.blockedIPMatcher
|
|
||||||
}
|
|
||||||
if h.config.IpsBlocked != nil && len(h.config.IpsBlocked.Rules) == 0 { // "ipsBlocked": []
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if inbound == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
switch inbound.Name {
|
|
||||||
case "vmess", "trojan", "hysteria", "wireguard":
|
|
||||||
errors.LogInfo(ctx, "applying default private IP blocking policy for inbound ", inbound.Name)
|
|
||||||
return defaultPrivateBlockIPMatcher
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(inbound.Name, "vless") || strings.HasPrefix(inbound.Name, "shadowsocks") {
|
|
||||||
errors.LogInfo(ctx, "applying default private IP blocking policy for inbound ", inbound.Name)
|
|
||||||
return defaultPrivateBlockIPMatcher
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isBlockedAddress(matcher geodata.IPMatcher, addr net.Address) bool {
|
|
||||||
return matcher != nil && addr != nil && addr.Family().IsIP() && matcher.Match(addr.IP())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process implements proxy.Outbound.
|
// Process implements proxy.Outbound.
|
||||||
func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error {
|
func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error {
|
||||||
outbounds := session.OutboundsFromContext(ctx)
|
outbounds := session.OutboundsFromContext(ctx)
|
||||||
@@ -147,7 +265,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
|||||||
ob.Name = "freedom"
|
ob.Name = "freedom"
|
||||||
ob.CanSpliceCopy = 1
|
ob.CanSpliceCopy = 1
|
||||||
inbound := session.InboundFromContext(ctx)
|
inbound := session.InboundFromContext(ctx)
|
||||||
blockedIPMatcher := h.getBlockedIPMatcher(ctx, inbound)
|
defaultRule := getDefaultFinalRule(inbound)
|
||||||
|
|
||||||
destination := ob.Target
|
destination := ob.Target
|
||||||
origTargetAddr := ob.OriginalTarget.Address
|
origTargetAddr := ob.OriginalTarget.Address
|
||||||
@@ -173,6 +291,9 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
|||||||
output := link.Writer
|
output := link.Writer
|
||||||
|
|
||||||
var conn stat.Connection
|
var conn stat.Connection
|
||||||
|
var blockedDest *net.Destination
|
||||||
|
var blockedRule *FinalRule
|
||||||
|
firstResolve := true
|
||||||
err := retry.ExponentialBackoff(5, 100).On(func() error {
|
err := retry.ExponentialBackoff(5, 100).On(func() error {
|
||||||
dialDest := destination
|
dialDest := destination
|
||||||
if h.config.DomainStrategy.HasStrategy() && dialDest.Address.Family().IsDomain() {
|
if h.config.DomainStrategy.HasStrategy() && dialDest.Address.Family().IsDomain() {
|
||||||
@@ -183,7 +304,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
|||||||
ips, err := internet.LookupForIP(dialDest.Address.Domain(), strategy, outGateway)
|
ips, err := internet.LookupForIP(dialDest.Address.Domain(), strategy, outGateway)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errors.LogInfoInner(ctx, err, "failed to get IP address for domain ", dialDest.Address.Domain())
|
errors.LogInfoInner(ctx, err, "failed to get IP address for domain ", dialDest.Address.Domain())
|
||||||
if h.config.DomainStrategy.ForceIP() {
|
if h.config.DomainStrategy.ForceIP() || h.shouldResolveDomainBeforeFinalRules(dialDest, defaultRule) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -194,6 +315,36 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
|||||||
}
|
}
|
||||||
errors.LogInfo(ctx, "dialing to ", dialDest)
|
errors.LogInfo(ctx, "dialing to ", dialDest)
|
||||||
}
|
}
|
||||||
|
} else if h.shouldResolveDomainBeforeFinalRules(dialDest, defaultRule) { // asis + domain + hasrules
|
||||||
|
domain := dialDest.Address.Domain()
|
||||||
|
var ips []net.IP
|
||||||
|
if firstResolve {
|
||||||
|
firstResolve = false
|
||||||
|
supportIPv4, supportIPv6 := utils.CheckRoutes()
|
||||||
|
if supportIPv4 {
|
||||||
|
ips, _ = net.DefaultResolver.LookupIP(ctx, "ip4", domain)
|
||||||
|
}
|
||||||
|
if len(ips) == 0 && supportIPv6 {
|
||||||
|
ips, _ = net.DefaultResolver.LookupIP(ctx, "ip6", domain)
|
||||||
|
}
|
||||||
|
if len(ips) == 0 {
|
||||||
|
return errors.New("failed to get IP address for domain ", domain)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ips, _ = net.DefaultResolver.LookupIP(ctx, "ip", domain)
|
||||||
|
}
|
||||||
|
if len(ips) == 0 { // SRV/TXT, lookup failed
|
||||||
|
return errors.New("failed to get IP address for domain ", domain)
|
||||||
|
}
|
||||||
|
if addr := net.IPAddress(ips[dice.Roll(len(ips))]); addr != nil {
|
||||||
|
dialDest.Address = addr
|
||||||
|
errors.LogInfo(ctx, "dialing to ", dialDest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if rule := h.matchFinalRule(dialDest.Network, dialDest.Address, dialDest.Port, defaultRule); rule != nil && rule.action == RuleAction_Block {
|
||||||
|
blockedDest = &dialDest
|
||||||
|
blockedRule = rule
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
rawConn, err := dialer.Dial(ctx, dialDest)
|
rawConn, err := dialer.Dial(ctx, dialDest)
|
||||||
@@ -207,9 +358,20 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("failed to open connection to ", destination).Base(err)
|
return errors.New("failed to open connection to ", destination).Base(err)
|
||||||
}
|
}
|
||||||
if remoteAddr := net.DestinationFromAddr(conn.RemoteAddr()).Address; isBlockedAddress(blockedIPMatcher, remoteAddr) {
|
if blockedDest != nil {
|
||||||
conn.Close()
|
delay := h.blockDelay(blockedRule)
|
||||||
return errors.New("blocked target IP: ", remoteAddr).AtInfo()
|
errors.LogInfo(ctx, "blocked target: ", *blockedDest, ", blackholing connection for ", delay)
|
||||||
|
timer := time.AfterFunc(delay, func() {
|
||||||
|
common.Interrupt(input)
|
||||||
|
common.Interrupt(output)
|
||||||
|
errors.LogInfo(ctx, "closed blackholed connection to blocked target: ", *blockedDest)
|
||||||
|
})
|
||||||
|
defer timer.Stop()
|
||||||
|
defer common.Close(output)
|
||||||
|
if err := buf.Copy(input, buf.Discard); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
if h.config.ProxyProtocol > 0 && h.config.ProxyProtocol <= 2 {
|
if h.config.ProxyProtocol > 0 && h.config.ProxyProtocol <= 2 {
|
||||||
version := byte(h.config.ProxyProtocol)
|
version := byte(h.config.ProxyProtocol)
|
||||||
@@ -255,7 +417,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
|||||||
writer = buf.NewWriter(conn)
|
writer = buf.NewWriter(conn)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
writer = NewPacketWriter(conn, h, UDPOverride, destination, blockedIPMatcher)
|
writer = NewPacketWriter(conn, h, defaultRule, UDPOverride, destination)
|
||||||
if h.config.Noises != nil {
|
if h.config.Noises != nil {
|
||||||
errors.LogDebug(ctx, "NOISE", h.config.Noises)
|
errors.LogDebug(ctx, "NOISE", h.config.Noises)
|
||||||
writer = &NoisePacketWriter{
|
writer = &NoisePacketWriter{
|
||||||
@@ -290,7 +452,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
|||||||
if destination.Network == net.Network_TCP {
|
if destination.Network == net.Network_TCP {
|
||||||
reader = buf.NewReader(conn)
|
reader = buf.NewReader(conn)
|
||||||
} else {
|
} else {
|
||||||
reader = NewPacketReader(conn, UDPOverride, destination, blockedIPMatcher)
|
reader = NewPacketReader(conn, h, defaultRule, UDPOverride, destination)
|
||||||
}
|
}
|
||||||
if err := buf.Copy(reader, output, buf.UpdateActivity(timer)); err != nil {
|
if err := buf.Copy(reader, output, buf.UpdateActivity(timer)); err != nil {
|
||||||
return errors.New("failed to process response").Base(err)
|
return errors.New("failed to process response").Base(err)
|
||||||
@@ -309,7 +471,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPacketReader(conn net.Conn, UDPOverride net.Destination, DialDest net.Destination, blockedIPMatcher geodata.IPMatcher) buf.Reader {
|
func NewPacketReader(conn net.Conn, h *Handler, defaultRule *FinalRule, UDPOverride net.Destination, DialDest net.Destination) buf.Reader {
|
||||||
iConn := conn
|
iConn := conn
|
||||||
statConn, ok := iConn.(*stat.CounterConnection)
|
statConn, ok := iConn.(*stat.CounterConnection)
|
||||||
if ok {
|
if ok {
|
||||||
@@ -328,7 +490,8 @@ func NewPacketReader(conn net.Conn, UDPOverride net.Destination, DialDest net.De
|
|||||||
return &PacketReader{
|
return &PacketReader{
|
||||||
PacketConnWrapper: c,
|
PacketConnWrapper: c,
|
||||||
Counter: counter,
|
Counter: counter,
|
||||||
BlockedIPMatcher: blockedIPMatcher,
|
Handler: h,
|
||||||
|
DefaultRule: defaultRule,
|
||||||
IsOverridden: isOverridden,
|
IsOverridden: isOverridden,
|
||||||
InitUnchangedAddr: DialDest.Address,
|
InitUnchangedAddr: DialDest.Address,
|
||||||
InitChangedAddr: net.DestinationFromAddr(conn.RemoteAddr()).Address,
|
InitChangedAddr: net.DestinationFromAddr(conn.RemoteAddr()).Address,
|
||||||
@@ -340,7 +503,8 @@ func NewPacketReader(conn net.Conn, UDPOverride net.Destination, DialDest net.De
|
|||||||
type PacketReader struct {
|
type PacketReader struct {
|
||||||
*internet.PacketConnWrapper
|
*internet.PacketConnWrapper
|
||||||
stats.Counter
|
stats.Counter
|
||||||
BlockedIPMatcher geodata.IPMatcher
|
Handler *Handler
|
||||||
|
DefaultRule *FinalRule
|
||||||
IsOverridden bool
|
IsOverridden bool
|
||||||
InitUnchangedAddr net.Address
|
InitUnchangedAddr net.Address
|
||||||
InitChangedAddr net.Address
|
InitChangedAddr net.Address
|
||||||
@@ -348,20 +512,19 @@ type PacketReader struct {
|
|||||||
|
|
||||||
func (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
func (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
||||||
b := buf.New()
|
b := buf.New()
|
||||||
|
b.Resize(0, buf.Size)
|
||||||
for {
|
for {
|
||||||
b.Resize(0, buf.Size)
|
|
||||||
n, d, err := r.PacketConnWrapper.ReadFrom(b.Bytes())
|
n, d, err := r.PacketConnWrapper.ReadFrom(b.Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Release()
|
b.Release()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
b.Resize(0, int32(n))
|
|
||||||
|
|
||||||
udpAddr := d.(*net.UDPAddr)
|
udpAddr := d.(*net.UDPAddr)
|
||||||
sourceAddr := net.IPAddress(udpAddr.IP)
|
sourceAddr := net.IPAddress(udpAddr.IP)
|
||||||
if isBlockedAddress(r.BlockedIPMatcher, sourceAddr) {
|
if r.Handler.applyFinalRules(net.Network_UDP, sourceAddr, net.Port(udpAddr.Port), r.DefaultRule) == RuleAction_Block {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
b.Resize(0, int32(n))
|
||||||
|
|
||||||
// if udp dest addr is changed, we are unable to get the correct src addr
|
// if udp dest addr is changed, we are unable to get the correct src addr
|
||||||
// so we don't attach src info to udp packet, break cone behavior, assuming the dial dest is the expected scr addr
|
// so we don't attach src info to udp packet, break cone behavior, assuming the dial dest is the expected scr addr
|
||||||
@@ -383,7 +546,7 @@ func (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DialDest means the dial target used in the dialer when creating conn
|
// DialDest means the dial target used in the dialer when creating conn
|
||||||
func NewPacketWriter(conn net.Conn, h *Handler, UDPOverride net.Destination, DialDest net.Destination, blockedIPMatcher geodata.IPMatcher) buf.Writer {
|
func NewPacketWriter(conn net.Conn, h *Handler, defaultRule *FinalRule, UDPOverride net.Destination, DialDest net.Destination) buf.Writer {
|
||||||
iConn := conn
|
iConn := conn
|
||||||
statConn, ok := iConn.(*stat.CounterConnection)
|
statConn, ok := iConn.(*stat.CounterConnection)
|
||||||
if ok {
|
if ok {
|
||||||
@@ -404,7 +567,7 @@ func NewPacketWriter(conn net.Conn, h *Handler, UDPOverride net.Destination, Dia
|
|||||||
PacketConnWrapper: c,
|
PacketConnWrapper: c,
|
||||||
Counter: counter,
|
Counter: counter,
|
||||||
Handler: h,
|
Handler: h,
|
||||||
BlockedIPMatcher: blockedIPMatcher,
|
DefaultRule: defaultRule,
|
||||||
UDPOverride: UDPOverride,
|
UDPOverride: UDPOverride,
|
||||||
ResolvedUDPAddr: resolvedUDPAddr,
|
ResolvedUDPAddr: resolvedUDPAddr,
|
||||||
LocalAddr: net.DestinationFromAddr(conn.LocalAddr()).Address,
|
LocalAddr: net.DestinationFromAddr(conn.LocalAddr()).Address,
|
||||||
@@ -418,8 +581,8 @@ type PacketWriter struct {
|
|||||||
*internet.PacketConnWrapper
|
*internet.PacketConnWrapper
|
||||||
stats.Counter
|
stats.Counter
|
||||||
*Handler
|
*Handler
|
||||||
BlockedIPMatcher geodata.IPMatcher
|
DefaultRule *FinalRule
|
||||||
UDPOverride net.Destination
|
UDPOverride net.Destination
|
||||||
|
|
||||||
// Dest of udp packets might be a domain, we will resolve them to IP
|
// Dest of udp packets might be a domain, we will resolve them to IP
|
||||||
// But resolver will return a random one if the domain has many IPs
|
// But resolver will return a random one if the domain has many IPs
|
||||||
@@ -477,7 +640,7 @@ func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if isBlockedAddress(w.BlockedIPMatcher, b.UDP.Address) {
|
if w.applyFinalRules(net.Network_UDP, b.UDP.Address, b.UDP.Port, w.DefaultRule) == RuleAction_Block {
|
||||||
b.Release()
|
b.Release()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
+44
-54
@@ -17,7 +17,6 @@ import (
|
|||||||
"github.com/xtls/xray-core/common/task"
|
"github.com/xtls/xray-core/common/task"
|
||||||
"github.com/xtls/xray-core/core"
|
"github.com/xtls/xray-core/core"
|
||||||
"github.com/xtls/xray-core/features/policy"
|
"github.com/xtls/xray-core/features/policy"
|
||||||
hyCtx "github.com/xtls/xray-core/proxy/hysteria/ctx"
|
|
||||||
"github.com/xtls/xray-core/transport"
|
"github.com/xtls/xray-core/transport"
|
||||||
"github.com/xtls/xray-core/transport/internet"
|
"github.com/xtls/xray-core/transport/internet"
|
||||||
"github.com/xtls/xray-core/transport/internet/hysteria"
|
"github.com/xtls/xray-core/transport/internet/hysteria"
|
||||||
@@ -56,7 +55,7 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter
|
|||||||
ob.CanSpliceCopy = 3
|
ob.CanSpliceCopy = 3
|
||||||
target := ob.Target
|
target := ob.Target
|
||||||
|
|
||||||
conn, err := dialer.Dial(hyCtx.ContextWithRequireDatagram(ctx, target.Network == net.Network_UDP), c.server.Destination)
|
conn, err := dialer.Dial(hysteria.ContextWithDatagram(ctx, target.Network == net.Network_UDP), c.server.Destination)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("failed to find an available destination").AtWarning().Base(err)
|
return errors.New("failed to find an available destination").AtWarning().Base(err)
|
||||||
}
|
}
|
||||||
@@ -118,7 +117,7 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter
|
|||||||
|
|
||||||
if target.Network == net.Network_UDP {
|
if target.Network == net.Network_UDP {
|
||||||
iConn := stat.TryUnwrapStatsConn(conn)
|
iConn := stat.TryUnwrapStatsConn(conn)
|
||||||
_, ok := iConn.(*hysteria.InterUdpConn)
|
_, ok := iConn.(*hysteria.InterConn)
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("udp requires hysteria udp transport")
|
return errors.New("udp requires hysteria udp transport")
|
||||||
}
|
}
|
||||||
@@ -127,8 +126,7 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter
|
|||||||
defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)
|
defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)
|
||||||
|
|
||||||
writer := &UDPWriter{
|
writer := &UDPWriter{
|
||||||
Writer: conn,
|
writer: conn,
|
||||||
buf: make([]byte, MaxUDPSize),
|
|
||||||
addr: target.NetAddr(),
|
addr: target.NetAddr(),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,8 +141,7 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter
|
|||||||
defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)
|
defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)
|
||||||
|
|
||||||
reader := &UDPReader{
|
reader := &UDPReader{
|
||||||
Reader: conn,
|
reader: conn,
|
||||||
buf: make([]byte, MaxUDPSize),
|
|
||||||
df: &Defragger{},
|
df: &Defragger{},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,28 +170,22 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type UDPWriter struct {
|
type UDPWriter struct {
|
||||||
Writer io.Writer
|
writer io.Writer
|
||||||
buf []byte
|
|
||||||
addr string
|
addr string
|
||||||
|
buf [buf.Size]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *UDPWriter) sendMsg(msg *UDPMessage) error {
|
func (w *UDPWriter) SendMessage(msg *UDPMessage) error {
|
||||||
msgN := msg.Serialize(w.buf)
|
msgN := msg.Serialize(w.buf[:])
|
||||||
if msgN < 0 {
|
if msgN < 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
_, err := w.Writer.Write(w.buf[:msgN])
|
_, err := w.writer.Write(w.buf[:msgN])
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *UDPWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
func (w *UDPWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
||||||
for {
|
for i, b := range mb {
|
||||||
mb2, b := buf.SplitFirst(mb)
|
|
||||||
mb = mb2
|
|
||||||
if b == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
addr := w.addr
|
addr := w.addr
|
||||||
if b.UDP != nil {
|
if b.UDP != nil {
|
||||||
addr = b.UDP.NetAddr()
|
addr = b.UDP.NetAddr()
|
||||||
@@ -209,22 +200,20 @@ func (w *UDPWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
|||||||
Data: b.Bytes(),
|
Data: b.Bytes(),
|
||||||
}
|
}
|
||||||
|
|
||||||
err := w.sendMsg(msg)
|
err := w.SendMessage(msg)
|
||||||
var errTooLarge *quic.DatagramTooLargeError
|
var errTooLarge *quic.DatagramTooLargeError
|
||||||
if go_errors.As(err, &errTooLarge) {
|
if go_errors.As(err, &errTooLarge) {
|
||||||
msg.PacketID = uint16(rand.Intn(0xFFFF)) + 1
|
msg.PacketID = uint16(rand.Intn(0xFFFF)) + 1
|
||||||
fMsgs := FragUDPMessage(msg, int(errTooLarge.MaxDatagramPayloadSize))
|
fMsgs := FragUDPMessage(msg, int(errTooLarge.MaxDatagramPayloadSize))
|
||||||
for _, fMsg := range fMsgs {
|
for _, fMsg := range fMsgs {
|
||||||
err := w.sendMsg(&fMsg)
|
err := w.SendMessage(&fMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Release()
|
buf.ReleaseMulti(mb[i:])
|
||||||
buf.ReleaseMulti(mb)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
b.Release()
|
buf.ReleaseMulti(mb[i:])
|
||||||
buf.ReleaseMulti(mb)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,34 +224,21 @@ func (w *UDPWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type UDPReader struct {
|
type UDPReader struct {
|
||||||
Reader io.Reader
|
reader io.Reader
|
||||||
buf []byte
|
df *Defragger
|
||||||
df *Defragger
|
firstBuf *buf.Buffer
|
||||||
firstMsg *UDPMessage
|
|
||||||
firstDest *net.Destination
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *UDPReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
func (r *UDPReader) ReadFrom(p []byte) (n int, addr *net.Destination, err error) {
|
||||||
if r.firstMsg != nil {
|
|
||||||
buffer := buf.New()
|
|
||||||
_, err := buffer.Write(r.firstMsg.Data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
buffer.UDP = r.firstDest
|
|
||||||
|
|
||||||
r.firstMsg = nil
|
|
||||||
r.firstDest = nil
|
|
||||||
|
|
||||||
return buf.MultiBuffer{buffer}, nil
|
|
||||||
}
|
|
||||||
for {
|
for {
|
||||||
n, err := r.Reader.Read(r.buf)
|
var buf [hysteria.MaxDatagramFrameSize]byte
|
||||||
|
|
||||||
|
n, err := r.reader.Read(buf[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
msg, err := ParseUDPMessage(r.buf[:n])
|
msg, err := ParseUDPMessage(buf[:n])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -274,17 +250,31 @@ func (r *UDPReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
|||||||
|
|
||||||
dest, err := net.ParseDestination("udp:" + dfMsg.Addr)
|
dest, err := net.ParseDestination("udp:" + dfMsg.Addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errors.LogDebug(context.Background(), dfMsg.Addr, " ParseDestination err ", err)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer := buf.New()
|
if len(p) < len(dfMsg.Data) {
|
||||||
if _, err := buffer.Write(dfMsg.Data); err != nil {
|
continue
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.UDP = &dest
|
return copy(p, dfMsg.Data), &dest, nil
|
||||||
|
|
||||||
return buf.MultiBuffer{buffer}, nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *UDPReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
||||||
|
if r.firstBuf != nil {
|
||||||
|
mb := buf.MultiBuffer{r.firstBuf}
|
||||||
|
r.firstBuf = nil
|
||||||
|
return mb, nil
|
||||||
|
}
|
||||||
|
b := buf.New()
|
||||||
|
b.Resize(0, buf.Size)
|
||||||
|
n, addr, err := r.ReadFrom(b.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
b.Release()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b.Resize(0, int32(n))
|
||||||
|
b.UDP = addr
|
||||||
|
return buf.MultiBuffer{b}, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,10 +1 @@
|
|||||||
package hysteria
|
package hysteria
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/xtls/xray-core/transport/internet/hysteria/padding"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
tcpRequestPadding = padding.Padding{Min: 64, Max: 512}
|
|
||||||
tcpResponsePadding = padding.Padding{Min: 128, Max: 1024}
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
package ctx
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/xtls/xray-core/proxy/hysteria/account"
|
|
||||||
)
|
|
||||||
|
|
||||||
type key int
|
|
||||||
|
|
||||||
const (
|
|
||||||
requireDatagram key = iota
|
|
||||||
validator
|
|
||||||
)
|
|
||||||
|
|
||||||
func ContextWithRequireDatagram(ctx context.Context, udp bool) context.Context {
|
|
||||||
if !udp {
|
|
||||||
return ctx
|
|
||||||
}
|
|
||||||
return context.WithValue(ctx, requireDatagram, struct{}{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func RequireDatagramFromContext(ctx context.Context) bool {
|
|
||||||
_, ok := ctx.Value(requireDatagram).(struct{})
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func ContextWithValidator(ctx context.Context, v *account.Validator) context.Context {
|
|
||||||
return context.WithValue(ctx, validator, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ValidatorFromContext(ctx context.Context) *account.Validator {
|
|
||||||
v, _ := ctx.Value(validator).(*account.Validator)
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
package hysteria
|
|
||||||
|
|
||||||
func FragUDPMessage(m *UDPMessage, maxSize int) []UDPMessage {
|
|
||||||
if m.Size() <= maxSize {
|
|
||||||
return []UDPMessage{*m}
|
|
||||||
}
|
|
||||||
fullPayload := m.Data
|
|
||||||
maxPayloadSize := maxSize - m.HeaderSize()
|
|
||||||
off := 0
|
|
||||||
fragID := uint8(0)
|
|
||||||
fragCount := uint8((len(fullPayload) + maxPayloadSize - 1) / maxPayloadSize) // round up
|
|
||||||
frags := make([]UDPMessage, fragCount)
|
|
||||||
for off < len(fullPayload) {
|
|
||||||
payloadSize := len(fullPayload) - off
|
|
||||||
if payloadSize > maxPayloadSize {
|
|
||||||
payloadSize = maxPayloadSize
|
|
||||||
}
|
|
||||||
frag := *m
|
|
||||||
frag.FragID = fragID
|
|
||||||
frag.FragCount = fragCount
|
|
||||||
frag.Data = fullPayload[off : off+payloadSize]
|
|
||||||
frags[fragID] = frag
|
|
||||||
off += payloadSize
|
|
||||||
fragID++
|
|
||||||
}
|
|
||||||
return frags
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defragger handles the defragmentation of UDP messages.
|
|
||||||
// The current implementation can only handle one packet ID at a time.
|
|
||||||
// If another packet arrives before a packet has received all fragments
|
|
||||||
// in their entirety, any previous state is discarded.
|
|
||||||
type Defragger struct {
|
|
||||||
pktID uint16
|
|
||||||
frags []*UDPMessage
|
|
||||||
count uint8
|
|
||||||
size int // data size
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Defragger) Feed(m *UDPMessage) *UDPMessage {
|
|
||||||
if m.FragCount <= 1 {
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
if m.FragID >= m.FragCount {
|
|
||||||
// wtf is this?
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if m.PacketID != d.pktID || m.FragCount != uint8(len(d.frags)) {
|
|
||||||
// new message, clear previous state
|
|
||||||
d.pktID = m.PacketID
|
|
||||||
d.frags = make([]*UDPMessage, m.FragCount)
|
|
||||||
d.frags[m.FragID] = m
|
|
||||||
d.count = 1
|
|
||||||
d.size = len(m.Data)
|
|
||||||
} else if d.frags[m.FragID] == nil {
|
|
||||||
d.frags[m.FragID] = m
|
|
||||||
d.count++
|
|
||||||
d.size += len(m.Data)
|
|
||||||
if int(d.count) == len(d.frags) {
|
|
||||||
// all fragments received, assemble
|
|
||||||
data := make([]byte, d.size)
|
|
||||||
off := 0
|
|
||||||
for _, frag := range d.frags {
|
|
||||||
off += copy(data[off:], frag.Data)
|
|
||||||
}
|
|
||||||
m.Data = data
|
|
||||||
m.FragID = 0
|
|
||||||
m.FragCount = 1
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
"github.com/apernet/quic-go/quicvarint"
|
"github.com/apernet/quic-go/quicvarint"
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/hysteria"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -17,8 +18,6 @@ const (
|
|||||||
MaxMessageLength = 2048
|
MaxMessageLength = 2048
|
||||||
MaxPaddingLength = 4096
|
MaxPaddingLength = 4096
|
||||||
|
|
||||||
MaxUDPSize = 4096
|
|
||||||
|
|
||||||
maxVarInt1 = 63
|
maxVarInt1 = 63
|
||||||
maxVarInt2 = 16383
|
maxVarInt2 = 16383
|
||||||
maxVarInt4 = 1073741823
|
maxVarInt4 = 1073741823
|
||||||
@@ -62,7 +61,7 @@ func ReadTCPRequest(r io.Reader) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func WriteTCPRequest(w io.Writer, addr string) error {
|
func WriteTCPRequest(w io.Writer, addr string) error {
|
||||||
padding := tcpRequestPadding.String()
|
padding := hysteria.TcpRequestPadding.String()
|
||||||
paddingLen := len(padding)
|
paddingLen := len(padding)
|
||||||
addrLen := len(addr)
|
addrLen := len(addr)
|
||||||
sz := int(quicvarint.Len(uint64(addrLen))) + addrLen +
|
sz := int(quicvarint.Len(uint64(addrLen))) + addrLen +
|
||||||
@@ -122,7 +121,7 @@ func ReadTCPResponse(r io.Reader) (bool, string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func WriteTCPResponse(w io.Writer, ok bool, msg string) error {
|
func WriteTCPResponse(w io.Writer, ok bool, msg string) error {
|
||||||
padding := tcpResponsePadding.String()
|
padding := hysteria.TcpResponsePadding.String()
|
||||||
paddingLen := len(padding)
|
paddingLen := len(padding)
|
||||||
msgLen := len(msg)
|
msgLen := len(msg)
|
||||||
sz := 1 + int(quicvarint.Len(uint64(msgLen))) + msgLen +
|
sz := 1 + int(quicvarint.Len(uint64(msgLen))) + msgLen +
|
||||||
@@ -247,3 +246,75 @@ func varintPut(b []byte, i uint64) int {
|
|||||||
}
|
}
|
||||||
panic(fmt.Sprintf("%#x doesn't fit into 62 bits", i))
|
panic(fmt.Sprintf("%#x doesn't fit into 62 bits", i))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func FragUDPMessage(m *UDPMessage, maxSize int) []UDPMessage {
|
||||||
|
if m.Size() <= maxSize {
|
||||||
|
return []UDPMessage{*m}
|
||||||
|
}
|
||||||
|
fullPayload := m.Data
|
||||||
|
maxPayloadSize := maxSize - m.HeaderSize()
|
||||||
|
off := 0
|
||||||
|
fragID := uint8(0)
|
||||||
|
fragCount := uint8((len(fullPayload) + maxPayloadSize - 1) / maxPayloadSize) // round up
|
||||||
|
frags := make([]UDPMessage, fragCount)
|
||||||
|
for off < len(fullPayload) {
|
||||||
|
payloadSize := len(fullPayload) - off
|
||||||
|
if payloadSize > maxPayloadSize {
|
||||||
|
payloadSize = maxPayloadSize
|
||||||
|
}
|
||||||
|
frag := *m
|
||||||
|
frag.FragID = fragID
|
||||||
|
frag.FragCount = fragCount
|
||||||
|
frag.Data = fullPayload[off : off+payloadSize]
|
||||||
|
frags[fragID] = frag
|
||||||
|
off += payloadSize
|
||||||
|
fragID++
|
||||||
|
}
|
||||||
|
return frags
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defragger handles the defragmentation of UDP messages.
|
||||||
|
// The current implementation can only handle one packet ID at a time.
|
||||||
|
// If another packet arrives before a packet has received all fragments
|
||||||
|
// in their entirety, any previous state is discarded.
|
||||||
|
type Defragger struct {
|
||||||
|
pktID uint16
|
||||||
|
frags []*UDPMessage
|
||||||
|
count uint8
|
||||||
|
size int // data size
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Defragger) Feed(m *UDPMessage) *UDPMessage {
|
||||||
|
if m.FragCount <= 1 {
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
if m.FragID >= m.FragCount {
|
||||||
|
// wtf is this?
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if m.PacketID != d.pktID || m.FragCount != uint8(len(d.frags)) {
|
||||||
|
// new message, clear previous state
|
||||||
|
d.pktID = m.PacketID
|
||||||
|
d.frags = make([]*UDPMessage, m.FragCount)
|
||||||
|
d.frags[m.FragID] = m
|
||||||
|
d.count = 1
|
||||||
|
d.size = len(m.Data)
|
||||||
|
} else if d.frags[m.FragID] == nil {
|
||||||
|
d.frags[m.FragID] = m
|
||||||
|
d.count++
|
||||||
|
d.size += len(m.Data)
|
||||||
|
if int(d.count) == len(d.frags) {
|
||||||
|
// all fragments received, assemble
|
||||||
|
data := make([]byte, d.size)
|
||||||
|
off := 0
|
||||||
|
for _, frag := range d.frags {
|
||||||
|
off += copy(data[off:], frag.Data)
|
||||||
|
}
|
||||||
|
m.Data = data
|
||||||
|
m.FragID = 0
|
||||||
|
m.FragCount = 1
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
+18
-43
@@ -2,7 +2,6 @@ package hysteria
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
@@ -91,54 +90,30 @@ func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Con
|
|||||||
inbound.User = v.User()
|
inbound.User = v.User()
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := iConn.(*hysteria.InterUdpConn); ok {
|
if _, ok := iConn.(*hysteria.InterConn); ok {
|
||||||
r := io.Reader(conn)
|
|
||||||
b := make([]byte, MaxUDPSize)
|
|
||||||
df := &Defragger{}
|
|
||||||
var firstMsg *UDPMessage
|
|
||||||
var firstDest net.Destination
|
|
||||||
|
|
||||||
for {
|
|
||||||
n, err := r.Read(b)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
msg, err := ParseUDPMessage(b[:n])
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
dfMsg := df.Feed(msg)
|
|
||||||
if dfMsg == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
firstMsg = dfMsg
|
|
||||||
firstDest, err = net.ParseDestination("udp:" + firstMsg.Addr)
|
|
||||||
if err != nil {
|
|
||||||
errors.LogDebug(context.Background(), dfMsg.Addr, " ParseDestination err ", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
reader := &UDPReader{
|
reader := &UDPReader{
|
||||||
Reader: r,
|
reader: conn,
|
||||||
buf: b,
|
df: &Defragger{},
|
||||||
df: df,
|
|
||||||
firstMsg: firstMsg,
|
|
||||||
firstDest: &firstDest,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
b := buf.New()
|
||||||
|
b.Resize(0, buf.Size)
|
||||||
|
n, addr, err := reader.ReadFrom(b.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
b.Release()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
b.Resize(0, int32(n))
|
||||||
|
b.UDP = addr
|
||||||
|
|
||||||
|
reader.firstBuf = b
|
||||||
|
|
||||||
writer := &UDPWriter{
|
writer := &UDPWriter{
|
||||||
Writer: conn,
|
writer: conn,
|
||||||
buf: make([]byte, MaxUDPSize),
|
addr: addr.NetAddr(),
|
||||||
addr: firstMsg.Addr,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return dispatcher.DispatchLink(ctx, firstDest, &transport.Link{
|
return dispatcher.DispatchLink(ctx, *addr, &transport.Link{
|
||||||
Reader: reader,
|
Reader: reader,
|
||||||
Writer: writer,
|
Writer: writer,
|
||||||
})
|
})
|
||||||
|
|||||||
+14
-78
@@ -4,13 +4,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
|
|
||||||
"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/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/net"
|
|
||||||
"github.com/xtls/xray-core/common/net/cnc"
|
|
||||||
"github.com/xtls/xray-core/common/retry"
|
|
||||||
"github.com/xtls/xray-core/common/session"
|
"github.com/xtls/xray-core/common/session"
|
||||||
"github.com/xtls/xray-core/common/task"
|
|
||||||
"github.com/xtls/xray-core/core"
|
"github.com/xtls/xray-core/core"
|
||||||
"github.com/xtls/xray-core/features/routing"
|
"github.com/xtls/xray-core/features/routing"
|
||||||
"github.com/xtls/xray-core/transport"
|
"github.com/xtls/xray-core/transport"
|
||||||
@@ -32,83 +27,24 @@ func (l *Loopback) Process(ctx context.Context, link *transport.Link, _ internet
|
|||||||
destination := ob.Target
|
destination := ob.Target
|
||||||
|
|
||||||
errors.LogInfo(ctx, "opening connection to ", destination)
|
errors.LogInfo(ctx, "opening connection to ", destination)
|
||||||
|
content := new(session.Content)
|
||||||
|
content.SkipDNSResolve = true
|
||||||
|
|
||||||
input := link.Reader
|
ctx = session.ContextWithContent(ctx, content)
|
||||||
output := link.Writer
|
inbound := &session.Inbound{}
|
||||||
|
originInbound := session.InboundFromContext(ctx)
|
||||||
|
if originInbound != nil {
|
||||||
|
// get a shallow copy to avoid modifying the inbound tag in upstream context
|
||||||
|
*inbound = *originInbound
|
||||||
|
}
|
||||||
|
inbound.Tag = l.config.InboundTag
|
||||||
|
ctx = session.ContextWithInbound(ctx, inbound)
|
||||||
|
|
||||||
var conn net.Conn
|
err := l.dispatcherInstance.DispatchLink(ctx, destination, link)
|
||||||
err := retry.ExponentialBackoff(2, 100).On(func() error {
|
|
||||||
dialDest := destination
|
|
||||||
|
|
||||||
content := new(session.Content)
|
|
||||||
content.SkipDNSResolve = true
|
|
||||||
|
|
||||||
ctx = session.ContextWithContent(ctx, content)
|
|
||||||
|
|
||||||
inbound := &session.Inbound{}
|
|
||||||
originInbound := session.InboundFromContext(ctx)
|
|
||||||
if originInbound != nil {
|
|
||||||
// get a shallow copy to avoid modifying the inbound tag in upstream context
|
|
||||||
*inbound = *originInbound
|
|
||||||
}
|
|
||||||
|
|
||||||
inbound.Tag = l.config.InboundTag
|
|
||||||
|
|
||||||
ctx = session.ContextWithInbound(ctx, inbound)
|
|
||||||
|
|
||||||
rawConn, err := l.dispatcherInstance.Dispatch(ctx, dialDest)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var readerOpt cnc.ConnectionOption
|
|
||||||
if dialDest.Network == net.Network_TCP {
|
|
||||||
readerOpt = cnc.ConnectionOutputMulti(rawConn.Reader)
|
|
||||||
} else {
|
|
||||||
readerOpt = cnc.ConnectionOutputMultiUDP(rawConn.Reader)
|
|
||||||
}
|
|
||||||
|
|
||||||
conn = cnc.NewConnection(cnc.ConnectionInputMulti(rawConn.Writer), readerOpt)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("failed to open connection to ", destination).Base(err)
|
errors.New(ctx, "failed to process loopback connection").Base(err)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
requestDone := func() error {
|
|
||||||
var writer buf.Writer
|
|
||||||
if destination.Network == net.Network_TCP {
|
|
||||||
writer = buf.NewWriter(conn)
|
|
||||||
} else {
|
|
||||||
writer = &buf.SequentialWriter{Writer: conn}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := buf.Copy(input, writer); err != nil {
|
|
||||||
return errors.New("failed to process request").Base(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
responseDone := func() error {
|
|
||||||
var reader buf.Reader
|
|
||||||
if destination.Network == net.Network_TCP {
|
|
||||||
reader = buf.NewReader(conn)
|
|
||||||
} else {
|
|
||||||
reader = buf.NewPacketReader(conn)
|
|
||||||
}
|
|
||||||
if err := buf.Copy(reader, output); err != nil {
|
|
||||||
return errors.New("failed to process response").Base(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := task.Run(ctx, requestDone, task.OnSuccess(responseDone, task.Close(output))); err != nil {
|
|
||||||
return errors.New("connection ends").Base(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-2
@@ -41,10 +41,12 @@ Here is simple Xray config snippet to enable the inbound:
|
|||||||
|
|
||||||
- IPv4 and IPv6
|
- IPv4 and IPv6
|
||||||
- TCP and UDP
|
- TCP and UDP
|
||||||
|
- ICMP Echo (ping)
|
||||||
|
|
||||||
## LIMITATION
|
## LIMITATION
|
||||||
|
|
||||||
- No ICMP support
|
- Only ICMP Echo request/reply is supported; other ICMP message types are ignored
|
||||||
|
- ICMP Echo replies are generated locally by the TUN stack; they do not validate real remote ICMP reachability
|
||||||
- Connections are established to any host, as connection success is only a mark of successful accepting packet for proxying. Hosts that are not accepting connections or don't even exists, will look like they opened a connection (SYN-ACK), and never send back a single byte, closing connection (RST) after some time. This is the side effect of the whole process actually being a proxy, and not real network layer 3 vpn
|
- Connections are established to any host, as connection success is only a mark of successful accepting packet for proxying. Hosts that are not accepting connections or don't even exists, will look like they opened a connection (SYN-ACK), and never send back a single byte, closing connection (RST) after some time. This is the side effect of the whole process actually being a proxy, and not real network layer 3 vpn
|
||||||
|
|
||||||
## CONSIDERATIONS
|
## CONSIDERATIONS
|
||||||
@@ -248,4 +250,4 @@ Set the environment variable `xray.tun.fd` (or `XRAY_TUN_FD`) to the fd number b
|
|||||||
Build using gomobile for iOS framework integration:
|
Build using gomobile for iOS framework integration:
|
||||||
```
|
```
|
||||||
gomobile bind -target=ios
|
gomobile bind -target=ios
|
||||||
```
|
```
|
||||||
|
|||||||
+60
-13
@@ -3,6 +3,8 @@ package tun
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
@@ -45,26 +47,53 @@ func (updater *InterfaceUpdater) Update() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var got *net.Interface
|
var got *net.Interface
|
||||||
for _, iface := range interfaces {
|
if updater.fixedName != "" {
|
||||||
if iface.Index == updater.tunIndex {
|
for _, iface := range interfaces {
|
||||||
continue
|
if iface.Index == updater.tunIndex {
|
||||||
}
|
continue
|
||||||
if updater.fixedName != "" {
|
}
|
||||||
if iface.Name == updater.fixedName {
|
if iface.Name == updater.fixedName {
|
||||||
got = &iface
|
got = &iface
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
addrs, err := iface.Addrs()
|
} else {
|
||||||
if err != nil {
|
var ifs []struct {
|
||||||
|
index int
|
||||||
|
score int
|
||||||
|
}
|
||||||
|
for i, iface := range interfaces {
|
||||||
|
if iface.Index == updater.tunIndex {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (iface.Flags&net.FlagUp != 0) &&
|
if strings.Contains(iface.Name, "vEthernet") {
|
||||||
(iface.Flags&net.FlagLoopback == 0) &&
|
continue
|
||||||
len(addrs) > 0 {
|
|
||||||
got = &iface
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
if iface.Flags&net.FlagUp == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if iface.Flags&net.FlagLoopback != 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
addrs, err := iface.Addrs()
|
||||||
|
if err != nil || len(addrs) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ifs = append(ifs, struct {
|
||||||
|
index int
|
||||||
|
score int
|
||||||
|
}{i, score(&iface, addrs)})
|
||||||
|
}
|
||||||
|
sort.Slice(ifs, func(i, j int) bool {
|
||||||
|
if ifs[i].score != ifs[j].score {
|
||||||
|
return ifs[i].score > ifs[j].score
|
||||||
|
}
|
||||||
|
|
||||||
|
return interfaces[ifs[i].index].Name < interfaces[ifs[j].index].Name
|
||||||
|
})
|
||||||
|
if len(ifs) > 0 {
|
||||||
|
iface := interfaces[ifs[0].index]
|
||||||
|
got = &iface
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,3 +105,21 @@ func (updater *InterfaceUpdater) Update() {
|
|||||||
updater.iface = got
|
updater.iface = got
|
||||||
errors.LogInfo(context.Background(), "[tun] update interface ", got.Name, " ", got.Index)
|
errors.LogInfo(context.Background(), "[tun] update interface ", got.Name, " ", got.Index)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func score(iface *net.Interface, addrs []net.Addr) int {
|
||||||
|
score := 0
|
||||||
|
|
||||||
|
name := strings.ToLower(iface.Name)
|
||||||
|
if strings.Contains(name, "wlan") || strings.Contains(name, "wi-fi") {
|
||||||
|
score += 2
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, addr := range addrs {
|
||||||
|
if strings.HasPrefix(addr.String(), "192.168.") {
|
||||||
|
score += 1
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return score
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
package icmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/checksum"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ProtocolLabel(netProto tcpip.NetworkProtocolNumber) string {
|
||||||
|
switch netProto {
|
||||||
|
case header.IPv4ProtocolNumber:
|
||||||
|
return "ipv4"
|
||||||
|
case header.IPv6ProtocolNumber:
|
||||||
|
return "ipv6"
|
||||||
|
default:
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseEchoRequest(netProto tcpip.NetworkProtocolNumber, message []byte) (uint16, uint16, bool) {
|
||||||
|
switch netProto {
|
||||||
|
case header.IPv4ProtocolNumber:
|
||||||
|
if len(message) < header.ICMPv4MinimumSize {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
icmpHdr := header.ICMPv4(message)
|
||||||
|
if icmpHdr.Type() != header.ICMPv4Echo || icmpHdr.Code() != header.ICMPv4UnusedCode {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
return icmpHdr.Ident(), icmpHdr.Sequence(), true
|
||||||
|
case header.IPv6ProtocolNumber:
|
||||||
|
if len(message) < header.ICMPv6MinimumSize {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
icmpHdr := header.ICMPv6(message)
|
||||||
|
if icmpHdr.Type() != header.ICMPv6EchoRequest || icmpHdr.Code() != header.ICMPv6UnusedCode {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
return icmpHdr.Ident(), icmpHdr.Sequence(), true
|
||||||
|
default:
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func RewriteChecksum(netProto tcpip.NetworkProtocolNumber, message []byte, srcIP, dstIP tcpip.Address) error {
|
||||||
|
switch netProto {
|
||||||
|
case header.IPv4ProtocolNumber:
|
||||||
|
if len(message) < header.ICMPv4MinimumSize {
|
||||||
|
return errors.New("invalid icmpv4 packet")
|
||||||
|
}
|
||||||
|
icmpHdr := header.ICMPv4(message)
|
||||||
|
icmpHdr.SetChecksum(0)
|
||||||
|
icmpHdr.SetChecksum(header.ICMPv4Checksum(icmpHdr[:header.ICMPv4MinimumSize], checksum.Checksum(icmpHdr.Payload(), 0)))
|
||||||
|
return nil
|
||||||
|
case header.IPv6ProtocolNumber:
|
||||||
|
if len(message) < header.ICMPv6MinimumSize {
|
||||||
|
return errors.New("invalid icmpv6 packet")
|
||||||
|
}
|
||||||
|
icmpHdr := header.ICMPv6(message)
|
||||||
|
icmpHdr.SetChecksum(0)
|
||||||
|
icmpHdr.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
|
||||||
|
Header: icmpHdr[:header.ICMPv6MinimumSize],
|
||||||
|
Src: srcIP,
|
||||||
|
Dst: dstIP,
|
||||||
|
PayloadCsum: checksum.Checksum(icmpHdr.Payload(), 0),
|
||||||
|
PayloadLen: len(icmpHdr.Payload()),
|
||||||
|
}))
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return errors.New("unsupported icmp network protocol")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuildLocalEchoReply(netProto tcpip.NetworkProtocolNumber, request []byte, srcIP, dstIP tcpip.Address) ([]byte, error) {
|
||||||
|
reply := append([]byte(nil), request...)
|
||||||
|
|
||||||
|
switch netProto {
|
||||||
|
case header.IPv4ProtocolNumber:
|
||||||
|
if len(reply) < header.ICMPv4MinimumSize {
|
||||||
|
return nil, errors.New("invalid icmpv4 echo packet")
|
||||||
|
}
|
||||||
|
icmpHdr := header.ICMPv4(reply)
|
||||||
|
if icmpHdr.Type() != header.ICMPv4Echo || icmpHdr.Code() != header.ICMPv4UnusedCode {
|
||||||
|
return nil, errors.New("not an icmpv4 echo request")
|
||||||
|
}
|
||||||
|
reply[0] = byte(header.ICMPv4EchoReply)
|
||||||
|
case header.IPv6ProtocolNumber:
|
||||||
|
if len(reply) < header.ICMPv6MinimumSize {
|
||||||
|
return nil, errors.New("invalid icmpv6 echo packet")
|
||||||
|
}
|
||||||
|
icmpHdr := header.ICMPv6(reply)
|
||||||
|
if icmpHdr.Type() != header.ICMPv6EchoRequest || icmpHdr.Code() != header.ICMPv6UnusedCode {
|
||||||
|
return nil, errors.New("not an icmpv6 echo request")
|
||||||
|
}
|
||||||
|
reply[0] = byte(header.ICMPv6EchoReply)
|
||||||
|
default:
|
||||||
|
return nil, errors.New("unsupported icmp network protocol")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := RewriteChecksum(netProto, reply, srcIP, dstIP); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return reply, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
package icmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/checksum"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseEchoRequest(t *testing.T) {
|
||||||
|
t.Run("ipv4 echo", func(t *testing.T) {
|
||||||
|
var zero tcpip.Address
|
||||||
|
packet := []byte{
|
||||||
|
byte(header.ICMPv4Echo), 0,
|
||||||
|
0, 0,
|
||||||
|
0x12, 0x34,
|
||||||
|
0x56, 0x78,
|
||||||
|
0xaa, 0xbb,
|
||||||
|
}
|
||||||
|
if err := RewriteChecksum(header.IPv4ProtocolNumber, packet, zero, zero); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ident, sequence, ok := ParseEchoRequest(header.IPv4ProtocolNumber, packet)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("expected ipv4 echo request to parse")
|
||||||
|
}
|
||||||
|
if ident != 0x1234 || sequence != 0x5678 {
|
||||||
|
t.Fatalf("unexpected ident/sequence: %x/%x", ident, sequence)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ipv6 echo", func(t *testing.T) {
|
||||||
|
packet := []byte{
|
||||||
|
byte(header.ICMPv6EchoRequest), 0,
|
||||||
|
0, 0,
|
||||||
|
0xab, 0xcd,
|
||||||
|
0xef, 0x01,
|
||||||
|
0xaa, 0xbb,
|
||||||
|
}
|
||||||
|
src := tcpip.AddrFromSlice([]byte{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1})
|
||||||
|
dst := tcpip.AddrFromSlice([]byte{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2})
|
||||||
|
if err := RewriteChecksum(header.IPv6ProtocolNumber, packet, src, dst); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ident, sequence, ok := ParseEchoRequest(header.IPv6ProtocolNumber, packet)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("expected ipv6 echo request to parse")
|
||||||
|
}
|
||||||
|
if ident != 0xabcd || sequence != 0xef01 {
|
||||||
|
t.Fatalf("unexpected ident/sequence: %x/%x", ident, sequence)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRewriteChecksum(t *testing.T) {
|
||||||
|
t.Run("ipv4", func(t *testing.T) {
|
||||||
|
var zero tcpip.Address
|
||||||
|
packet := []byte{
|
||||||
|
byte(header.ICMPv4Echo), 0,
|
||||||
|
0xff, 0xff,
|
||||||
|
0x12, 0x34,
|
||||||
|
0x56, 0x78,
|
||||||
|
0xaa, 0xbb, 0xcc,
|
||||||
|
}
|
||||||
|
if err := RewriteChecksum(header.IPv4ProtocolNumber, packet, zero, zero); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
icmpHdr := header.ICMPv4(packet)
|
||||||
|
if got, want := icmpHdr.Checksum(), header.ICMPv4Checksum(icmpHdr[:header.ICMPv4MinimumSize], checksumPayloadV4(icmpHdr.Payload())); got != want {
|
||||||
|
t.Fatalf("unexpected ipv4 checksum: got %x want %x", got, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ipv6", func(t *testing.T) {
|
||||||
|
packet := []byte{
|
||||||
|
byte(header.ICMPv6EchoReply), 0,
|
||||||
|
0xff, 0xff,
|
||||||
|
0x12, 0x34,
|
||||||
|
0x56, 0x78,
|
||||||
|
0xaa, 0xbb, 0xcc,
|
||||||
|
}
|
||||||
|
src := tcpip.AddrFromSlice([]byte{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1})
|
||||||
|
dst := tcpip.AddrFromSlice([]byte{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2})
|
||||||
|
if err := RewriteChecksum(header.IPv6ProtocolNumber, packet, src, dst); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
icmpHdr := header.ICMPv6(packet)
|
||||||
|
want := header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
|
||||||
|
Header: icmpHdr[:header.ICMPv6MinimumSize],
|
||||||
|
Src: src,
|
||||||
|
Dst: dst,
|
||||||
|
PayloadLen: len(icmpHdr.Payload()),
|
||||||
|
PayloadCsum: checksumPayloadV6(icmpHdr.Payload()),
|
||||||
|
})
|
||||||
|
if got := icmpHdr.Checksum(); got != want {
|
||||||
|
t.Fatalf("unexpected ipv6 checksum: got %x want %x", got, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildLocalEchoReply(t *testing.T) {
|
||||||
|
t.Run("ipv4", func(t *testing.T) {
|
||||||
|
request := []byte{
|
||||||
|
byte(header.ICMPv4Echo), 0,
|
||||||
|
0, 0,
|
||||||
|
0x12, 0x34,
|
||||||
|
0x56, 0x78,
|
||||||
|
0xaa, 0xbb, 0xcc,
|
||||||
|
}
|
||||||
|
src := tcpip.Address{}
|
||||||
|
dst := tcpip.Address{}
|
||||||
|
if err := RewriteChecksum(header.IPv4ProtocolNumber, request, src, dst); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
reply, err := BuildLocalEchoReply(header.IPv4ProtocolNumber, request, dst, src)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if request[0] != byte(header.ICMPv4Echo) {
|
||||||
|
t.Fatal("request mutated")
|
||||||
|
}
|
||||||
|
icmpHdr := header.ICMPv4(reply)
|
||||||
|
if icmpHdr.Type() != header.ICMPv4EchoReply || icmpHdr.Code() != header.ICMPv4UnusedCode {
|
||||||
|
t.Fatalf("unexpected ipv4 reply type/code: %d/%d", icmpHdr.Type(), icmpHdr.Code())
|
||||||
|
}
|
||||||
|
if icmpHdr.Ident() != 0x1234 || icmpHdr.Sequence() != 0x5678 {
|
||||||
|
t.Fatalf("unexpected ipv4 ident/sequence: %x/%x", icmpHdr.Ident(), icmpHdr.Sequence())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ipv6", func(t *testing.T) {
|
||||||
|
request := []byte{
|
||||||
|
byte(header.ICMPv6EchoRequest), 0,
|
||||||
|
0, 0,
|
||||||
|
0xab, 0xcd,
|
||||||
|
0xef, 0x01,
|
||||||
|
0xaa, 0xbb, 0xcc,
|
||||||
|
}
|
||||||
|
src := tcpip.AddrFromSlice([]byte{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1})
|
||||||
|
dst := tcpip.AddrFromSlice([]byte{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2})
|
||||||
|
if err := RewriteChecksum(header.IPv6ProtocolNumber, request, src, dst); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
reply, err := BuildLocalEchoReply(header.IPv6ProtocolNumber, request, dst, src)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if request[0] != byte(header.ICMPv6EchoRequest) {
|
||||||
|
t.Fatal("request mutated")
|
||||||
|
}
|
||||||
|
icmpHdr := header.ICMPv6(reply)
|
||||||
|
if icmpHdr.Type() != header.ICMPv6EchoReply || icmpHdr.Code() != header.ICMPv6UnusedCode {
|
||||||
|
t.Fatalf("unexpected ipv6 reply type/code: %d/%d", icmpHdr.Type(), icmpHdr.Code())
|
||||||
|
}
|
||||||
|
if icmpHdr.Ident() != 0xabcd || icmpHdr.Sequence() != 0xef01 {
|
||||||
|
t.Fatalf("unexpected ipv6 ident/sequence: %x/%x", icmpHdr.Ident(), icmpHdr.Sequence())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func checksumPayloadV4(payload []byte) uint16 {
|
||||||
|
return checksum.Checksum(payload, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checksumPayloadV6(payload []byte) uint16 {
|
||||||
|
return checksum.Checksum(payload, 0)
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
|
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
|
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
|
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
|
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
|
||||||
"gvisor.dev/gvisor/pkg/waiter"
|
"gvisor.dev/gvisor/pkg/waiter"
|
||||||
@@ -117,6 +118,8 @@ func (t *stackGVisor) Start() error {
|
|||||||
udpForwarder.HandlePacket(src, dst, data)
|
udpForwarder.HandlePacket(src, dst, data)
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber4, t.handleICMPv4Packet)
|
||||||
|
ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber6, t.handleICMPv6Packet)
|
||||||
|
|
||||||
t.stack = ipStack
|
t.stack = ipStack
|
||||||
t.endpoint = linkEndpoint
|
t.endpoint = linkEndpoint
|
||||||
@@ -205,7 +208,7 @@ func (t *stackGVisor) Close() error {
|
|||||||
func createStack(ep stack.LinkEndpoint) (*stack.Stack, error) {
|
func createStack(ep stack.LinkEndpoint) (*stack.Stack, error) {
|
||||||
opts := stack.Options{
|
opts := stack.Options{
|
||||||
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
|
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
|
||||||
TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol},
|
TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol, icmp.NewProtocol4, icmp.NewProtocol6},
|
||||||
HandleLocal: false,
|
HandleLocal: false,
|
||||||
}
|
}
|
||||||
gStack := stack.New(opts)
|
gStack := stack.New(opts)
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
package tun
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
tunicmp "github.com/xtls/xray-core/proxy/tun/icmp"
|
||||||
|
"gvisor.dev/gvisor/pkg/buffer"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (t *stackGVisor) handleICMPv4Packet(id stack.TransportEndpointID, pkt *stack.PacketBuffer) bool {
|
||||||
|
return t.handleICMPEchoPacket(header.IPv4ProtocolNumber, id, pkt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *stackGVisor) handleICMPv6Packet(id stack.TransportEndpointID, pkt *stack.PacketBuffer) bool {
|
||||||
|
return t.handleICMPEchoPacket(header.IPv6ProtocolNumber, id, pkt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *stackGVisor) handleICMPEchoPacket(netProto tcpip.NetworkProtocolNumber, id stack.TransportEndpointID, pkt *stack.PacketBuffer) bool {
|
||||||
|
srcIP := id.RemoteAddress
|
||||||
|
dstIP := id.LocalAddress
|
||||||
|
if srcIP.Len() == 0 || dstIP.Len() == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
message := transportPacketBytes(pkt)
|
||||||
|
ident, sequence, ok := tunicmp.ParseEchoRequest(netProto, message)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
reply, err := tunicmp.BuildLocalEchoReply(netProto, message, dstIP, srcIP)
|
||||||
|
if err != nil {
|
||||||
|
errors.LogInfoInner(t.ctx, err, "[tun] failed to build local icmp echo reply")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
errors.LogDebug(t.ctx, "[tun][icmp] ", tunicmp.ProtocolLabel(netProto), " local echo reply ", dstIP, " -> ", srcIP, " id=", ident, " seq=", sequence)
|
||||||
|
if err := t.writeRawICMPPacket(netProto, reply, dstIP, srcIP); err != nil {
|
||||||
|
errors.LogInfoInner(t.ctx, err, "[tun] failed to write local icmp echo reply")
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *stackGVisor) writeRawICMPPacket(netProto tcpip.NetworkProtocolNumber, message []byte, srcIP, dstIP tcpip.Address) error {
|
||||||
|
ipHeaderSize := header.IPv6MinimumSize
|
||||||
|
ipProtocol := header.IPv6ProtocolNumber
|
||||||
|
transportProtocol := header.ICMPv6ProtocolNumber
|
||||||
|
if netProto == header.IPv4ProtocolNumber {
|
||||||
|
ipHeaderSize = header.IPv4MinimumSize
|
||||||
|
ipProtocol = header.IPv4ProtocolNumber
|
||||||
|
transportProtocol = header.ICMPv4ProtocolNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||||
|
ReserveHeaderBytes: ipHeaderSize,
|
||||||
|
Payload: buffer.MakeWithData(message),
|
||||||
|
})
|
||||||
|
defer pkt.DecRef()
|
||||||
|
|
||||||
|
if netProto == header.IPv4ProtocolNumber {
|
||||||
|
ipHdr := header.IPv4(pkt.NetworkHeader().Push(header.IPv4MinimumSize))
|
||||||
|
ipHdr.Encode(&header.IPv4Fields{
|
||||||
|
TotalLength: uint16(header.IPv4MinimumSize + len(message)),
|
||||||
|
TTL: 64,
|
||||||
|
Protocol: uint8(transportProtocol),
|
||||||
|
SrcAddr: srcIP,
|
||||||
|
DstAddr: dstIP,
|
||||||
|
})
|
||||||
|
ipHdr.SetChecksum(^ipHdr.CalculateChecksum())
|
||||||
|
} else {
|
||||||
|
ipHdr := header.IPv6(pkt.NetworkHeader().Push(header.IPv6MinimumSize))
|
||||||
|
ipHdr.Encode(&header.IPv6Fields{
|
||||||
|
PayloadLength: uint16(len(message)),
|
||||||
|
TransportProtocol: transportProtocol,
|
||||||
|
HopLimit: 64,
|
||||||
|
SrcAddr: srcIP,
|
||||||
|
DstAddr: dstIP,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := t.stack.WriteRawPacket(defaultNIC, ipProtocol, buffer.MakeWithView(pkt.ToView())); err != nil {
|
||||||
|
return errors.New("failed to write raw icmp packet back to stack", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func transportPacketBytes(pkt *stack.PacketBuffer) []byte {
|
||||||
|
headerBytes := pkt.TransportHeader().Slice()
|
||||||
|
payloadBytes := pkt.Data().AsRange().ToSlice()
|
||||||
|
message := make([]byte, len(headerBytes)+len(payloadBytes))
|
||||||
|
copy(message, headerBytes)
|
||||||
|
copy(message[len(headerBytes):], payloadBytes)
|
||||||
|
return message
|
||||||
|
}
|
||||||
@@ -177,9 +177,6 @@ func (t *WindowsTun) Start() error {
|
|||||||
|
|
||||||
if updater != nil {
|
if updater != nil {
|
||||||
t.changeCallback, err = winipcfg.RegisterInterfaceChangeCallback(func(notificationType winipcfg.MibNotificationType, iface *winipcfg.MibIPInterfaceRow) {
|
t.changeCallback, err = winipcfg.RegisterInterfaceChangeCallback(func(notificationType winipcfg.MibNotificationType, iface *winipcfg.MibIPInterfaceRow) {
|
||||||
if notificationType != winipcfg.MibDeleteInstance {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
updater.Update()
|
updater.Update()
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ func (x *Fallback) GetXver() uint64 {
|
|||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Clients []*protocol.User `protobuf:"bytes,1,rep,name=clients,proto3" json:"clients,omitempty"`
|
Users []*protocol.User `protobuf:"bytes,1,rep,name=users,proto3" json:"users,omitempty"`
|
||||||
Fallbacks []*Fallback `protobuf:"bytes,2,rep,name=fallbacks,proto3" json:"fallbacks,omitempty"`
|
Fallbacks []*Fallback `protobuf:"bytes,2,rep,name=fallbacks,proto3" json:"fallbacks,omitempty"`
|
||||||
Decryption string `protobuf:"bytes,3,opt,name=decryption,proto3" json:"decryption,omitempty"`
|
Decryption string `protobuf:"bytes,3,opt,name=decryption,proto3" json:"decryption,omitempty"`
|
||||||
XorMode uint32 `protobuf:"varint,4,opt,name=xorMode,proto3" json:"xorMode,omitempty"`
|
XorMode uint32 `protobuf:"varint,4,opt,name=xorMode,proto3" json:"xorMode,omitempty"`
|
||||||
@@ -149,9 +149,9 @@ func (*Config) Descriptor() ([]byte, []int) {
|
|||||||
return file_proxy_vless_inbound_config_proto_rawDescGZIP(), []int{1}
|
return file_proxy_vless_inbound_config_proto_rawDescGZIP(), []int{1}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Config) GetClients() []*protocol.User {
|
func (x *Config) GetUsers() []*protocol.User {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Clients
|
return x.Users
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -209,9 +209,9 @@ const file_proxy_vless_inbound_config_proto_rawDesc = "" +
|
|||||||
"\x04path\x18\x03 \x01(\tR\x04path\x12\x12\n" +
|
"\x04path\x18\x03 \x01(\tR\x04path\x12\x12\n" +
|
||||||
"\x04type\x18\x04 \x01(\tR\x04type\x12\x12\n" +
|
"\x04type\x18\x04 \x01(\tR\x04type\x12\x12\n" +
|
||||||
"\x04dest\x18\x05 \x01(\tR\x04dest\x12\x12\n" +
|
"\x04dest\x18\x05 \x01(\tR\x04dest\x12\x12\n" +
|
||||||
"\x04xver\x18\x06 \x01(\x04R\x04xver\"\x96\x02\n" +
|
"\x04xver\x18\x06 \x01(\x04R\x04xver\"\x92\x02\n" +
|
||||||
"\x06Config\x124\n" +
|
"\x06Config\x120\n" +
|
||||||
"\aclients\x18\x01 \x03(\v2\x1a.xray.common.protocol.UserR\aclients\x12@\n" +
|
"\x05users\x18\x01 \x03(\v2\x1a.xray.common.protocol.UserR\x05users\x12@\n" +
|
||||||
"\tfallbacks\x18\x02 \x03(\v2\".xray.proxy.vless.inbound.FallbackR\tfallbacks\x12\x1e\n" +
|
"\tfallbacks\x18\x02 \x03(\v2\".xray.proxy.vless.inbound.FallbackR\tfallbacks\x12\x1e\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"decryption\x18\x03 \x01(\tR\n" +
|
"decryption\x18\x03 \x01(\tR\n" +
|
||||||
@@ -242,7 +242,7 @@ var file_proxy_vless_inbound_config_proto_goTypes = []any{
|
|||||||
(*protocol.User)(nil), // 2: xray.common.protocol.User
|
(*protocol.User)(nil), // 2: xray.common.protocol.User
|
||||||
}
|
}
|
||||||
var file_proxy_vless_inbound_config_proto_depIdxs = []int32{
|
var file_proxy_vless_inbound_config_proto_depIdxs = []int32{
|
||||||
2, // 0: xray.proxy.vless.inbound.Config.clients:type_name -> xray.common.protocol.User
|
2, // 0: xray.proxy.vless.inbound.Config.users:type_name -> xray.common.protocol.User
|
||||||
0, // 1: xray.proxy.vless.inbound.Config.fallbacks:type_name -> xray.proxy.vless.inbound.Fallback
|
0, // 1: xray.proxy.vless.inbound.Config.fallbacks:type_name -> xray.proxy.vless.inbound.Fallback
|
||||||
2, // [2:2] is the sub-list for method output_type
|
2, // [2:2] is the sub-list for method output_type
|
||||||
2, // [2:2] is the sub-list for method input_type
|
2, // [2:2] is the sub-list for method input_type
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ message Fallback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message Config {
|
message Config {
|
||||||
repeated xray.common.protocol.User clients = 1;
|
repeated xray.common.protocol.User users = 1;
|
||||||
repeated Fallback fallbacks = 2;
|
repeated Fallback fallbacks = 2;
|
||||||
|
|
||||||
string decryption = 3;
|
string decryption = 3;
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ func init() {
|
|||||||
c := config.(*Config)
|
c := config.(*Config)
|
||||||
|
|
||||||
validator := new(vless.MemoryValidator)
|
validator := new(vless.MemoryValidator)
|
||||||
for _, user := range c.Clients {
|
for _, user := range c.Users {
|
||||||
u, err := user.ToMemoryUser()
|
u, err := user.ToMemoryUser()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("failed to get VLESS user").Base(err).AtError()
|
return nil, errors.New("failed to get VLESS user").Base(err).AtError()
|
||||||
|
|||||||
@@ -78,9 +78,9 @@ func (d *deviceNet) DialUDPAddrPort(laddr, raddr netip.AddrPort) (net.Conn, erro
|
|||||||
var conn net.PacketConn
|
var conn net.PacketConn
|
||||||
var err error
|
var err error
|
||||||
if raddr.Addr().Is4() {
|
if raddr.Addr().Is4() {
|
||||||
conn, err = d.lc.ListenPacket(context.Background(), "udp4", ":0")
|
conn, err = d.lc.ListenPacket(context.Background(), "udp", "0.0.0.0:0")
|
||||||
} else {
|
} else {
|
||||||
conn, err = d.lc.ListenPacket(context.Background(), "udp6", ":0")
|
conn, err = d.lc.ListenPacket(context.Background(), "udp", "[::]:0")
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user