mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-10-21 16:54:29 +00:00
Compare commits
28
Commits
main
..
lua-sandbox
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bba0ae39d1 | ||
|
|
03ee3f872f | ||
|
|
65449d4f03 | ||
|
|
d5bfb0a5c7 | ||
|
|
afa28af4e0 | ||
|
|
ecd8719d98 | ||
|
|
74456ca5e9 | ||
|
|
b408f7f0c7 | ||
|
|
ef4f4888df | ||
|
|
c2a9b074f4 | ||
|
|
9726bbc9d7 | ||
|
|
6cd0c8a414 | ||
|
|
796bbd763a | ||
|
|
e04191e65b | ||
|
|
bd28a87f0b | ||
|
|
a04770587e | ||
|
|
eb9c6030ab | ||
|
|
e4124d3dbb | ||
|
|
4c0f3a94c5 | ||
|
|
19842ed0c2 | ||
|
|
34efc32ff0 | ||
|
|
4828ed8270 | ||
|
|
e84f3065dc | ||
|
|
fa9dc21a55 | ||
|
|
590a5236bb | ||
|
|
1aae252fc3 | ||
|
|
20c4c8f956 | ||
|
|
4c571f5e06 |
@@ -9,8 +9,8 @@ ARG TARGETARCH
|
|||||||
RUN GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 go build -o xray -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags "-s -w -buildid=" ./main
|
RUN GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 go build -o xray -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags "-s -w -buildid=" ./main
|
||||||
|
|
||||||
# Download geodat into a staging directory
|
# Download geodat into a staging directory
|
||||||
ADD https://raw.githubusercontent.com/XTLS/Xray-rules-dat/release/geoip/geoip-full.dat /tmp/geodat/geoip.dat
|
ADD https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/geoip.dat /tmp/geodat/geoip.dat
|
||||||
ADD https://raw.githubusercontent.com/XTLS/Xray-rules-dat/release/geosite/geosite-expand.dat /tmp/geodat/geosite.dat
|
ADD https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/geosite.dat /tmp/geodat/geosite.dat
|
||||||
|
|
||||||
RUN mkdir -p /tmp/empty
|
RUN mkdir -p /tmp/empty
|
||||||
|
|
||||||
@@ -40,15 +40,21 @@ RUN echo '{}' >/tmp/usr/local/etc/xray/11_geodata.json
|
|||||||
RUN echo '{}' >/tmp/usr/local/etc/xray/12_env.json
|
RUN echo '{}' >/tmp/usr/local/etc/xray/12_env.json
|
||||||
RUN echo '{}' >/tmp/usr/local/etc/xray/99_version.json
|
RUN echo '{}' >/tmp/usr/local/etc/xray/99_version.json
|
||||||
|
|
||||||
|
# Create log files
|
||||||
|
RUN mkdir -p /tmp/var/log/xray && touch \
|
||||||
|
/tmp/var/log/xray/access.log \
|
||||||
|
/tmp/var/log/xray/error.log
|
||||||
|
|
||||||
# Build finally image
|
# Build finally image
|
||||||
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=65532:65532 --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=65532:65532 --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:65532 --chmod=750 /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:65532 --chmod=640 /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=65532:65532 --chmod=700 /tmp/empty /var/log/xray
|
COPY --from=build --chown=0:0 --chmod=755 /tmp/empty /var/log/xray
|
||||||
|
COPY --from=build --chown=65532:65532 --chmod=600 /tmp/var/log/xray/*.log /var/log/xray/
|
||||||
|
|
||||||
VOLUME /usr/local/etc/xray
|
VOLUME /usr/local/etc/xray
|
||||||
VOLUME /var/log/xray
|
VOLUME /var/log/xray
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ ARG TARGETARCH
|
|||||||
RUN GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 go build -o xray -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags "-s -w -buildid=" ./main
|
RUN GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 go build -o xray -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags "-s -w -buildid=" ./main
|
||||||
|
|
||||||
# Download geodat into a staging directory
|
# Download geodat into a staging directory
|
||||||
ADD https://raw.githubusercontent.com/XTLS/Xray-rules-dat/release/geoip/geoip-full.dat /tmp/geodat/geoip.dat
|
ADD https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/geoip.dat /tmp/geodat/geoip.dat
|
||||||
ADD https://raw.githubusercontent.com/XTLS/Xray-rules-dat/release/geosite/geosite-expand.dat /tmp/geodat/geosite.dat
|
ADD https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/geosite.dat /tmp/geodat/geosite.dat
|
||||||
|
|
||||||
RUN mkdir -p /tmp/empty
|
RUN mkdir -p /tmp/empty
|
||||||
|
|
||||||
@@ -40,6 +40,11 @@ RUN echo '{}' >/tmp/usr/local/etc/xray/11_geodata.json
|
|||||||
RUN echo '{}' >/tmp/usr/local/etc/xray/12_env.json
|
RUN echo '{}' >/tmp/usr/local/etc/xray/12_env.json
|
||||||
RUN echo '{}' >/tmp/usr/local/etc/xray/99_version.json
|
RUN echo '{}' >/tmp/usr/local/etc/xray/99_version.json
|
||||||
|
|
||||||
|
# Create log files
|
||||||
|
RUN mkdir -p /tmp/var/log/xray && touch \
|
||||||
|
/tmp/var/log/xray/access.log \
|
||||||
|
/tmp/var/log/xray/error.log
|
||||||
|
|
||||||
# Build finally image
|
# Build finally image
|
||||||
# Note on Distroless Base Image and Architecture Support:
|
# Note on Distroless Base Image and Architecture Support:
|
||||||
# - The official 'gcr.io/distroless/static' image provided by Google only supports a limited set of architectures for Linux:
|
# - The official 'gcr.io/distroless/static' image provided by Google only supports a limited set of architectures for Linux:
|
||||||
@@ -55,9 +60,10 @@ 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=65532:65532 --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=65532:65532 --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:65532 --chmod=750 /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:65532 --chmod=640 /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=65532:65532 --chmod=700 /tmp/empty /var/log/xray
|
COPY --from=build --chown=0:0 --chmod=755 /tmp/empty /var/log/xray
|
||||||
|
COPY --from=build --chown=65532:65532 --chmod=600 /tmp/var/log/xray/*.log /var/log/xray/
|
||||||
|
|
||||||
VOLUME /usr/local/etc/xray
|
VOLUME /usr/local/etc/xray
|
||||||
VOLUME /var/log/xray
|
VOLUME /var/log/xray
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ jobs:
|
|||||||
max_attempts: 60
|
max_attempts: 60
|
||||||
command: |
|
command: |
|
||||||
[ -d 'resources' ] || mkdir resources
|
[ -d 'resources' ] || mkdir resources
|
||||||
LIST=('XTLS Xray-rules-dat geoip/geoip-lite geoip' 'XTLS Xray-rules-dat geosite/geosite geosite')
|
LIST=('Loyalsoldier v2ray-rules-dat geoip geoip' 'Loyalsoldier v2ray-rules-dat geosite geosite')
|
||||||
for i in "${LIST[@]}"
|
for i in "${LIST[@]}"
|
||||||
do
|
do
|
||||||
INFO=($(echo $i | awk 'BEGIN{FS=" ";OFS=" "} {print $1,$2,$3,$4}'))
|
INFO=($(echo $i | awk 'BEGIN{FS=" ";OFS=" "} {print $1,$2,$3,$4}'))
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ func TestDNSScriptGeoIPFallback(t *testing.T) {
|
|||||||
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
||||||
script := `
|
script := `
|
||||||
local servers = require("xray.dns").Servers
|
local servers = require("xray.dns").Servers
|
||||||
local ru_ips = require("xray.geodata").BuildIPMatcher("geoip:ru")
|
local us_ips = require("xray.geodata").BuildIPMatcher("geoip:us")
|
||||||
|
|
||||||
local by_id = {}
|
local by_id = {}
|
||||||
for _, server in ipairs(servers) do
|
for _, server in ipairs(servers) do
|
||||||
@@ -138,7 +138,7 @@ assert(by_id.primary and by_id.fallback, "primary and fallback DNS servers are r
|
|||||||
|
|
||||||
function HandleDNSQuery(domain, ipv4, ipv6, fake)
|
function HandleDNSQuery(domain, ipv4, ipv6, fake)
|
||||||
local ips, ttl, err = by_id.primary:Query(domain, ipv4, ipv6, fake)
|
local ips, ttl, err = by_id.primary:Query(domain, ipv4, ipv6, fake)
|
||||||
if not err and ru_ips:AnyMatch(ips) then
|
if not err and us_ips:AnyMatch(ips) then
|
||||||
return ips, ttl, nil
|
return ips, ttl, nil
|
||||||
end
|
end
|
||||||
return by_id.fallback:Query(domain, ipv4, ipv6, fake)
|
return by_id.fallback:Query(domain, ipv4, ipv6, fake)
|
||||||
@@ -152,7 +152,7 @@ end
|
|||||||
primary := &scriptNameServer{
|
primary := &scriptNameServer{
|
||||||
name: "primary",
|
name: "primary",
|
||||||
answers: map[string]net.IP{
|
answers: map[string]net.IP{
|
||||||
"ru.example": net.ParseIP("2a02:6b8::feed:0ff"),
|
"us.example": net.ParseIP("2001:4860:4860::8888"),
|
||||||
"other.example": net.ParseIP("127.0.0.1"),
|
"other.example": net.ParseIP("127.0.0.1"),
|
||||||
},
|
},
|
||||||
ttl: 30,
|
ttl: 30,
|
||||||
@@ -191,7 +191,7 @@ end
|
|||||||
ip net.IP
|
ip net.IP
|
||||||
ttl uint32
|
ttl uint32
|
||||||
}{
|
}{
|
||||||
{"Ru.Example.", net.ParseIP("2a02:6b8::feed:0ff"), 30},
|
{"Us.Example.", net.ParseIP("2001:4860:4860::8888"), 30},
|
||||||
{"other.example", net.ParseIP("9.9.9.9"), 60},
|
{"other.example", net.ParseIP("9.9.9.9"), 60},
|
||||||
} {
|
} {
|
||||||
ips, ttl, err := server.LookupIP(tc.domain, option)
|
ips, ttl, err := server.LookupIP(tc.domain, option)
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ func (h *HealthPingRTTS) getStatistics() *HealthPingStats {
|
|||||||
stats.Min = 0
|
stats.Min = 0
|
||||||
return stats
|
return stats
|
||||||
}
|
}
|
||||||
stats.Average = sum / time.Duration(cnt)
|
stats.Average = time.Duration(int(sum) / cnt)
|
||||||
var std float64
|
var std float64
|
||||||
if cnt < 2 {
|
if cnt < 2 {
|
||||||
// no enough data for standard deviation, we assume it's half of the average rtt
|
// no enough data for standard deviation, we assume it's half of the average rtt
|
||||||
|
|||||||
@@ -415,7 +415,7 @@ func BenchmarkMphDomainMatcher(b *testing.B) {
|
|||||||
|
|
||||||
func BenchmarkMultiGeoIPMatcher(b *testing.B) {
|
func BenchmarkMultiGeoIPMatcher(b *testing.B) {
|
||||||
b.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
b.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
||||||
rules, err := geodata.ParseIPRules([]string{"geoip:cn", "geoip:ir", "geoip:ru"})
|
rules, err := geodata.ParseIPRules([]string{"geoip:cn", "geoip:jp", "geoip:ca", "geoip:us"})
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
matcher, err := NewIPMatcher(rules, MatcherAsType_Target)
|
matcher, err := NewIPMatcher(rules, MatcherAsType_Target)
|
||||||
|
|||||||
@@ -400,13 +400,13 @@ func TestIPMatcher4CN(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIPMatcher6RU(t *testing.T) {
|
func TestIPMatcher6US(t *testing.T) {
|
||||||
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
||||||
|
|
||||||
matcher := buildIPMatcher("geoip:ru")
|
matcher := buildIPMatcher("geoip:us")
|
||||||
|
|
||||||
if !matcher.Match(xnet.ParseAddress("2a02:6b8::feed:0ff").IP()) {
|
if !matcher.Match(xnet.ParseAddress("2001:4860:4860::8888").IP()) {
|
||||||
t.Error("expect RU geoip contain 2a02:6b8::feed:0ff, but actually not")
|
t.Error("expect US geoip contain 2001:4860:4860::8888, but actually not")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -423,11 +423,11 @@ func BenchmarkIPMatcher4CN(b *testing.B) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkIPMatcher6RU(b *testing.B) {
|
func BenchmarkIPMatcher6US(b *testing.B) {
|
||||||
b.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
b.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
||||||
|
|
||||||
matcher := buildIPMatcher("geoip:ru")
|
matcher := buildIPMatcher("geoip:us")
|
||||||
ip := xnet.ParseAddress("2a02:6b8::feed:0ff").IP()
|
ip := xnet.ParseAddress("2001:4860:4860::8888").IP()
|
||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ func TestParseIPRules(t *testing.T) {
|
|||||||
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
||||||
|
|
||||||
rules := []string{
|
rules := []string{
|
||||||
"geoip:ru",
|
"geoip:us",
|
||||||
"geoip:cn",
|
"geoip:cn",
|
||||||
"!geoip:cn",
|
"!geoip:cn",
|
||||||
"!!geoip:cn",
|
"!!geoip:cn",
|
||||||
@@ -20,9 +20,9 @@ func TestParseIPRules(t *testing.T) {
|
|||||||
"!geoip:!cn",
|
"!geoip:!cn",
|
||||||
"ext:geoip.dat:!cn",
|
"ext:geoip.dat:!cn",
|
||||||
"ext:geoip.dat:!!cn",
|
"ext:geoip.dat:!!cn",
|
||||||
"ext:geoip.dat:ir",
|
"ext:geoip.dat:ca",
|
||||||
"ext-ip:geoip.dat:!cn",
|
"ext-ip:geoip.dat:!cn",
|
||||||
"ext-ip:geoip.dat:!ir",
|
"ext-ip:geoip.dat:!ca",
|
||||||
"192.168.0.0/24",
|
"192.168.0.0/24",
|
||||||
"!192.168.0.0/24",
|
"!192.168.0.0/24",
|
||||||
"!!192.168.0.0/24",
|
"!!192.168.0.0/24",
|
||||||
|
|||||||
@@ -26,20 +26,20 @@ func FindProcess(network, srcIP string, srcPort uint16, destIP string, destPort
|
|||||||
panic("Unsupported network type for process lookup.")
|
panic("Unsupported network type for process lookup.")
|
||||||
}
|
}
|
||||||
|
|
||||||
var procFiles []string
|
var procFile string
|
||||||
|
|
||||||
switch network {
|
switch network {
|
||||||
case "tcp":
|
case "tcp":
|
||||||
if net.ParseIP(srcIP).To4() != nil {
|
if net.ParseIP(srcIP).To4() != nil {
|
||||||
procFiles = []string{"/proc/net/tcp"}
|
procFile = "/proc/net/tcp"
|
||||||
} else {
|
} else {
|
||||||
procFiles = []string{"/proc/net/tcp6"}
|
procFile = "/proc/net/tcp6"
|
||||||
}
|
}
|
||||||
case "udp":
|
case "udp":
|
||||||
if net.ParseIP(srcIP).To4() != nil {
|
if net.ParseIP(srcIP).To4() != nil {
|
||||||
procFiles = []string{"/proc/net/udp", "/proc/net/udp6"}
|
procFile = "/proc/net/udp"
|
||||||
} else {
|
} else {
|
||||||
procFiles = []string{"/proc/net/udp6", "/proc/net/udp"}
|
procFile = "/proc/net/udp6"
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
panic("Unsupported network type for process lookup.")
|
panic("Unsupported network type for process lookup.")
|
||||||
@@ -50,17 +50,12 @@ func FindProcess(network, srcIP string, srcPort uint16, destIP string, destPort
|
|||||||
return 0, "", "", errors.New("failed to format address: ", err)
|
return 0, "", "", errors.New("failed to format address: ", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
portFallback := ""
|
inode, err := findInodeInFile(procFile, targetHexAddr)
|
||||||
if network == "udp" {
|
|
||||||
portFallback = fmt.Sprintf(":%04X", srcPort)
|
|
||||||
}
|
|
||||||
|
|
||||||
inode, err := findInodeInFiles(procFiles, targetHexAddr, portFallback)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, "", "", errors.New("could not search in ", strings.Join(procFiles, " and ")).Base(err)
|
return 0, "", "", errors.New("could not search in ", procFile).Base(err)
|
||||||
}
|
}
|
||||||
if inode == "" {
|
if inode == "" {
|
||||||
return 0, "", "", errors.New("connection for ", srcIP, ":", srcPort, " not found in ", strings.Join(procFiles, " or "))
|
return 0, "", "", errors.New("connection for ", srcIP, ":", srcPort, " not found in ", procFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
pidStr, err := findPidByInode(inode)
|
pidStr, err := findPidByInode(inode)
|
||||||
@@ -107,43 +102,31 @@ func formatLittleEndianString(addr net.IP, port Port) (string, error) {
|
|||||||
return fmt.Sprintf("%s:%s", ipHex, portHex), nil
|
return fmt.Sprintf("%s:%s", ipHex, portHex), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func findInodeInFiles(filePaths []string, targetHexAddr, portFallback string) (string, error) {
|
func findInodeInFile(filePath, targetHexAddr string) (string, error) {
|
||||||
onlyMatchedPort := ""
|
|
||||||
|
|
||||||
for i, filePath := range filePaths {
|
|
||||||
file, err := os.Open(filePath)
|
file, err := os.Open(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if i > 0 && os.IsNotExist(err) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
scanner := bufio.NewScanner(file)
|
scanner := bufio.NewScanner(file)
|
||||||
|
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
fields := strings.Fields(scanner.Text())
|
line := scanner.Text()
|
||||||
|
fields := strings.Fields(line)
|
||||||
|
|
||||||
if len(fields) < 10 {
|
if len(fields) < 10 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
localAddress := fields[1]
|
localAddress := fields[1]
|
||||||
if localAddress == targetHexAddr {
|
if localAddress == targetHexAddr {
|
||||||
return fields[9], nil
|
inode := fields[9]
|
||||||
}
|
return inode, nil
|
||||||
if portFallback != "" && strings.HasSuffix(localAddress, portFallback) {
|
|
||||||
wildcardIP := strings.TrimSuffix(localAddress, portFallback)
|
|
||||||
if (len(wildcardIP) == 8 || len(wildcardIP) == 32) && strings.Trim(wildcardIP, "0") == "" {
|
|
||||||
onlyMatchedPort = fields[9]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := scanner.Err(); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return onlyMatchedPort, nil
|
return "", scanner.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
func findPidByInode(inode string) (string, error) {
|
func findPidByInode(inode string) (string, error) {
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,7 @@ import (
|
|||||||
var (
|
var (
|
||||||
Version_x byte = 26
|
Version_x byte = 26
|
||||||
Version_y byte = 10
|
Version_y byte = 10
|
||||||
Version_z byte = 21
|
Version_z byte = 10
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -4,12 +4,13 @@ go 1.27
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/apernet/quic-go v0.61.1-0.20260806010916-184d081eef3e
|
github.com/apernet/quic-go v0.61.1-0.20260806010916-184d081eef3e
|
||||||
|
github.com/cloudflare/circl v1.6.5
|
||||||
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
|
||||||
github.com/google/go-cmp v0.7.0
|
github.com/google/go-cmp v0.7.0
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/gorilla/websocket v1.5.3
|
github.com/gorilla/websocket v1.5.3
|
||||||
github.com/klauspost/cpuid/v2 v2.4.1
|
github.com/klauspost/cpuid/v2 v2.4.0
|
||||||
github.com/libp2p/go-nat v1.0.1-0.20250821073202-01afc089f138
|
github.com/libp2p/go-nat v1.0.1-0.20250821073202-01afc089f138
|
||||||
github.com/miekg/dns v1.1.73
|
github.com/miekg/dns v1.1.73
|
||||||
github.com/pelletier/go-toml v1.9.5
|
github.com/pelletier/go-toml v1.9.5
|
||||||
@@ -19,20 +20,21 @@ require (
|
|||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/stretchr/testify v1.12.1
|
github.com/stretchr/testify v1.12.1
|
||||||
github.com/vishvananda/netlink v1.3.1
|
github.com/vishvananda/netlink v1.3.1
|
||||||
github.com/xtls/reality v0.0.0-20261014101318-83f621788ca8
|
github.com/xtls/reality v0.0.0-20260908062103-8cdf7bf9c7f0
|
||||||
github.com/yuin/gopher-lua v1.1.2
|
github.com/yuin/gopher-lua v1.1.2
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||||
golang.org/x/crypto v0.58.0
|
golang.org/x/crypto v0.57.0
|
||||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20261020153306-9f2b28d4331a
|
golang.org/x/crypto/x509roots/fallback v0.0.0-20261005185213-c3db4df58582
|
||||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
|
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
|
||||||
golang.org/x/net v0.61.0
|
golang.org/x/net v0.60.0
|
||||||
golang.org/x/sync v0.24.0
|
golang.org/x/sync v0.23.0
|
||||||
golang.org/x/sys v0.49.0
|
golang.org/x/sys v0.48.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 v1.1.1
|
golang.zx2c4.com/wireguard/windows v1.1.1
|
||||||
google.golang.org/grpc v1.84.0
|
google.golang.org/grpc v1.84.0
|
||||||
google.golang.org/protobuf v1.36.12
|
google.golang.org/protobuf v1.36.12
|
||||||
|
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0
|
||||||
h12.io/socks v1.0.3
|
h12.io/socks v1.0.3
|
||||||
layeh.com/gopher-luar v1.0.11
|
layeh.com/gopher-luar v1.0.11
|
||||||
lukechampine.com/blake3 v1.4.1
|
lukechampine.com/blake3 v1.4.1
|
||||||
@@ -40,12 +42,13 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/andybalholm/brotli v1.2.3 // indirect
|
github.com/andybalholm/brotli v1.0.6 // indirect
|
||||||
|
github.com/google/btree v1.1.2 // indirect
|
||||||
github.com/google/gopacket v1.1.19 // indirect
|
github.com/google/gopacket v1.1.19 // indirect
|
||||||
github.com/huin/goupnp v1.2.0 // indirect
|
github.com/huin/goupnp v1.2.0 // indirect
|
||||||
github.com/jackpal/go-nat-pmp v1.0.2 // indirect
|
github.com/jackpal/go-nat-pmp v1.0.2 // indirect
|
||||||
github.com/juju/ratelimit v1.0.2 // indirect
|
github.com/juju/ratelimit v1.0.2 // indirect
|
||||||
github.com/klauspost/compress v1.20.0 // indirect
|
github.com/klauspost/compress v1.17.4 // indirect
|
||||||
github.com/koron/go-ssdp v0.0.4 // indirect
|
github.com/koron/go-ssdp v0.0.4 // indirect
|
||||||
github.com/libp2p/go-netroute v0.2.1 // indirect
|
github.com/libp2p/go-netroute v0.2.1 // indirect
|
||||||
github.com/pion/dtls/v3 v3.1.5 // indirect
|
github.com/pion/dtls/v3 v3.1.5 // indirect
|
||||||
@@ -55,9 +58,9 @@ require (
|
|||||||
github.com/vishvananda/netns v0.0.5 // indirect
|
github.com/vishvananda/netns v0.0.5 // indirect
|
||||||
github.com/wlynxg/anet v0.0.5 // indirect
|
github.com/wlynxg/anet v0.0.5 // indirect
|
||||||
go.yaml.in/yaml/v3 v3.0.5 // indirect
|
go.yaml.in/yaml/v3 v3.0.5 // indirect
|
||||||
golang.org/x/text v0.43.0 // indirect
|
golang.org/x/text v0.42.0 // indirect
|
||||||
golang.org/x/tools v0.51.0 // indirect
|
golang.org/x/time v0.14.0 // indirect
|
||||||
|
golang.org/x/tools v0.49.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0 // indirect
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
github.com/andybalholm/brotli v1.2.3 h1:8H1qwOkl2LPfjf3YezB90JnCliZb6SInJ/OJkEbA5NQ=
|
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
|
||||||
github.com/andybalholm/brotli v1.2.3/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||||
github.com/apernet/quic-go v0.61.1-0.20260806010916-184d081eef3e h1:5mgtR5gwIgBKMiGI1QdXldZZ+SNor06Nbu1wCBulQBg=
|
github.com/apernet/quic-go v0.61.1-0.20260806010916-184d081eef3e h1:5mgtR5gwIgBKMiGI1QdXldZZ+SNor06Nbu1wCBulQBg=
|
||||||
github.com/apernet/quic-go v0.61.1-0.20260806010916-184d081eef3e/go.mod h1:x7qxEvX6MCVtDuBKHj3E+88+BtrbEMuAL5qGUKItjW8=
|
github.com/apernet/quic-go v0.61.1-0.20260806010916-184d081eef3e/go.mod h1:x7qxEvX6MCVtDuBKHj3E+88+BtrbEMuAL5qGUKItjW8=
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
|
github.com/cloudflare/circl v1.6.5 h1:O64F26HEqNhznd/hrC5KZXVKYuKM2rx4deZDTc4ihQA=
|
||||||
|
github.com/cloudflare/circl v1.6.5/go.mod h1:h5LNyxAc5nTue9DS5jT+48en2PSDYt3zdGnz5OstK6c=
|
||||||
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
|
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
|
||||||
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
|
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
|
||||||
github.com/go-quicktest/qt v1.102.0 h1:HSQxCeh5YZH3EL3W39ixjtyaEhcWSXQHtHnMBzSs474=
|
github.com/go-quicktest/qt v1.102.0 h1:HSQxCeh5YZH3EL3W39ixjtyaEhcWSXQHtHnMBzSs474=
|
||||||
@@ -31,10 +33,10 @@ github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7Bd
|
|||||||
github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
|
github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
|
||||||
github.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI=
|
github.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI=
|
||||||
github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=
|
github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=
|
||||||
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
||||||
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||||
github.com/klauspost/cpuid/v2 v2.4.1 h1:I9KxadAkvo+QjtcEE2f2tyfEvmIla/T0m+14PrZNl0U=
|
github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw=
|
||||||
github.com/klauspost/cpuid/v2 v2.4.1/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU=
|
github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU=
|
||||||
github.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0=
|
github.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0=
|
||||||
github.com/koron/go-ssdp v0.0.4/go.mod h1:oDXq+E5IL5q0U8uSBcoAXzTzInwy5lEgC91HoKtbmZk=
|
github.com/koron/go-ssdp v0.0.4/go.mod h1:oDXq+E5IL5q0U8uSBcoAXzTzInwy5lEgC91HoKtbmZk=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
@@ -79,10 +81,8 @@ github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zd
|
|||||||
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||||
github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
|
github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
|
||||||
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
|
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
|
||||||
github.com/xtls/reality v0.0.0-20261014101318-83f621788ca8 h1:RjLnVvlzK4q6zWaSBPNRGsrgx2fbJDeUWffjw574uzs=
|
github.com/xtls/reality v0.0.0-20260908062103-8cdf7bf9c7f0 h1:rb+fKQFhz+5I2PPuQsNYxI5mUU840XWYtRF0ZBjvkws=
|
||||||
github.com/xtls/reality v0.0.0-20261014101318-83f621788ca8/go.mod h1:ptlSkJ8k/7IDauNH0SA96JPOo+nYGLTv2Y3UMpwvAsY=
|
github.com/xtls/reality v0.0.0-20260908062103-8cdf7bf9c7f0/go.mod h1:DsJblcWDGt76+FVqBVwbwRhxyyNJsGV48gJLch0OOWI=
|
||||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
|
||||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
|
||||||
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=
|
||||||
github.com/yuin/gopher-lua v0.0.0-20190206043414-8bfc7677f583/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
|
github.com/yuin/gopher-lua v0.0.0-20190206043414-8bfc7677f583/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
|
||||||
github.com/yuin/gopher-lua v1.1.2 h1:yF/FjE3hD65tBbt0VXLE13HWS9h34fdzJmrWRXwobGA=
|
github.com/yuin/gopher-lua v1.1.2 h1:yF/FjE3hD65tBbt0VXLE13HWS9h34fdzJmrWRXwobGA=
|
||||||
@@ -95,10 +95,10 @@ go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBs
|
|||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.58.0 h1:COkYLr4k7nDI4r6QtMML2AfzeIswWHAJthLS6K/M/54=
|
golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M=
|
||||||
golang.org/x/crypto v0.58.0/go.mod h1:Xh+kl5A+M0gMiWDytH2Hhr5Z7WbNCpv/eB/EFgfSk+w=
|
golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA=
|
||||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20261020153306-9f2b28d4331a h1:V5oicZXS0oqZx30gY/apgSVD7YvNBfrIOdcMnlelJY4=
|
golang.org/x/crypto/x509roots/fallback v0.0.0-20261005185213-c3db4df58582 h1:wjDBrGbfLuifgrVLFEWUBJYAfh5Q1wkMc5t0FY0tCbs=
|
||||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20261020153306-9f2b28d4331a/go.mod h1:HPze8vhfG6fO06AM+VSvxRm4E3+5Yk375mgrJ5M2z1E=
|
golang.org/x/crypto/x509roots/fallback v0.0.0-20261005185213-c3db4df58582/go.mod h1:HPze8vhfG6fO06AM+VSvxRm4E3+5Yk375mgrJ5M2z1E=
|
||||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
|
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
|
||||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
@@ -107,12 +107,12 @@ golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
|||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.61.0 h1:RR+6j/BTBCrggOLugxa+66R8FvoN6MHlzDlZIbN9YFw=
|
golang.org/x/net v0.60.0 h1:79p50tfZlm0J9YfoDsSi639qSXNGVwEzOPLCxM2FsYU=
|
||||||
golang.org/x/net v0.61.0/go.mod h1:WgDCOTH8iwtB66T4/GfI94AHo/hhdb7Hah9NDseQfiI=
|
golang.org/x/net v0.60.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.24.0 h1:iqiDrGoDgyuvAKrhLQthUEzU/R378N78gXbBjxed7oA=
|
golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk=
|
||||||
golang.org/x/sync v0.24.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
|
golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
|
||||||
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -121,22 +121,22 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.49.0 h1:XbzkgYJHdqh/8m2Uu0W/dQv8nktxx4BFHp1M0gROTXA=
|
golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo=
|
||||||
golang.org/x/sys v0.49.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
|
golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.43.0 h1:1QivrlhwAsnMOcqgOdl9my7s9OyZl8O5/UGUTJVXoWc=
|
golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI=
|
||||||
golang.org/x/text v0.43.0/go.mod h1:hbSIYA/amXcRXTFSZqh8tWHEoqRvvbKPJ2uz/KU+a+s=
|
golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E=
|
||||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
||||||
golang.org/x/tools v0.51.0 h1:k4Xc/1Om9jwkBJBo4NVLMSARBoWtK10mx+W5BnXCeAI=
|
golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI=
|
||||||
golang.org/x/tools v0.51.0/go.mod h1:9eEncMayCV6zRMGhR5eZEC2iBx98qWcF1HZ9Z7wJOoA=
|
golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
|||||||
@@ -9,17 +9,12 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
googleuuid "github.com/google/uuid"
|
googleuuid "github.com/google/uuid"
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/geodata"
|
|
||||||
xlua "github.com/xtls/xray-core/common/lua"
|
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/transport/internet"
|
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask/filter"
|
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask/fragment"
|
"github.com/xtls/xray-core/transport/internet/finalmask/fragment"
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
|
"github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask/mkcp/aes128gcm"
|
"github.com/xtls/xray-core/transport/internet/finalmask/mkcp/aes128gcm"
|
||||||
@@ -33,7 +28,6 @@ import (
|
|||||||
"github.com/xtls/xray-core/transport/internet/finalmask/xdns"
|
"github.com/xtls/xray-core/transport/internet/finalmask/xdns"
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask/xicmp"
|
"github.com/xtls/xray-core/transport/internet/finalmask/xicmp"
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask/xmc"
|
"github.com/xtls/xray-core/transport/internet/finalmask/xmc"
|
||||||
"github.com/xtls/xray-core/transport/internet/hysteria/congestion/bbr"
|
|
||||||
"github.com/xtls/xray-core/transport/internet/tls"
|
"github.com/xtls/xray-core/transport/internet/tls"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
@@ -76,7 +70,6 @@ var (
|
|||||||
customVarNamePattern = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`)
|
customVarNamePattern = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`)
|
||||||
|
|
||||||
tcpmaskLoader = NewJSONConfigLoader(ConfigCreatorCache{
|
tcpmaskLoader = NewJSONConfigLoader(ConfigCreatorCache{
|
||||||
"filter": func() interface{} { return new(Filter) },
|
|
||||||
"header-custom": func() interface{} { return new(HeaderCustomTCP) },
|
"header-custom": func() interface{} { return new(HeaderCustomTCP) },
|
||||||
"fragment": func() interface{} { return new(FragmentMask) },
|
"fragment": func() interface{} { return new(FragmentMask) },
|
||||||
"sudoku": func() interface{} { return new(Sudoku) },
|
"sudoku": func() interface{} { return new(Sudoku) },
|
||||||
@@ -84,7 +77,6 @@ var (
|
|||||||
}, "type", "settings")
|
}, "type", "settings")
|
||||||
|
|
||||||
udpmaskLoader = NewJSONConfigLoader(ConfigCreatorCache{
|
udpmaskLoader = NewJSONConfigLoader(ConfigCreatorCache{
|
||||||
"filter": func() interface{} { return new(Filter) },
|
|
||||||
"header-custom": func() interface{} { return new(HeaderCustomUDP) },
|
"header-custom": func() interface{} { return new(HeaderCustomUDP) },
|
||||||
"mkcp-legacy": func() interface{} { return new(MkcpLegacy) },
|
"mkcp-legacy": func() interface{} { return new(MkcpLegacy) },
|
||||||
"noise": func() interface{} { return new(NoiseMask) },
|
"noise": func() interface{} { return new(NoiseMask) },
|
||||||
@@ -97,23 +89,6 @@ var (
|
|||||||
}, "type", "settings")
|
}, "type", "settings")
|
||||||
)
|
)
|
||||||
|
|
||||||
type Filter struct {
|
|
||||||
BlockedSourceIPs []string `json:"blockedSourceIPs"`
|
|
||||||
AllowedSourceIPs []string `json:"allowedSourceIPs"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Filter) Build() (proto.Message, error) {
|
|
||||||
rules, err := geodata.ParseIPRules(c.BlockedSourceIPs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New(`invalid "blockedSourceIPs"`).Base(err)
|
|
||||||
}
|
|
||||||
allowed, err := geodata.ParseIPRules(c.AllowedSourceIPs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New(`invalid "allowedSourceIPs"`).Base(err)
|
|
||||||
}
|
|
||||||
return &filter.Config{BlockedSourceIps: rules, AllowedSourceIps: allowed}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type TCPItem struct {
|
type TCPItem struct {
|
||||||
Delay Int32Range `json:"delay"`
|
Delay Int32Range `json:"delay"`
|
||||||
Rand int32 `json:"rand"`
|
Rand int32 `json:"rand"`
|
||||||
@@ -331,24 +306,9 @@ type NoiseItem struct {
|
|||||||
type NoiseMask struct {
|
type NoiseMask struct {
|
||||||
Reset Int32Range `json:"reset"`
|
Reset Int32Range `json:"reset"`
|
||||||
Noise []NoiseItem `json:"noise"`
|
Noise []NoiseItem `json:"noise"`
|
||||||
Script string `json:"script"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *NoiseMask) Build() (proto.Message, error) {
|
func (c *NoiseMask) Build() (proto.Message, error) {
|
||||||
if c.Script != "" {
|
|
||||||
if len(c.Noise) > 0 {
|
|
||||||
return nil, errors.New(`"script" and "noise" can't be used together`)
|
|
||||||
}
|
|
||||||
if _, err := xlua.Compile(c.Script); err != nil {
|
|
||||||
return nil, errors.New("failed to compile the noise script").Base(err)
|
|
||||||
}
|
|
||||||
return &noise.Config{
|
|
||||||
ResetMin: int64(c.Reset.From),
|
|
||||||
ResetMax: int64(c.Reset.To),
|
|
||||||
Script: c.Script,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
noiseSlice := make([]*noise.Item, 0, len(c.Noise))
|
noiseSlice := make([]*noise.Item, 0, len(c.Noise))
|
||||||
for _, item := range c.Noise {
|
for _, item := range c.Noise {
|
||||||
if len(item.Packet) > 0 && item.Rand.To > 0 {
|
if len(item.Packet) > 0 && item.Rand.To > 0 {
|
||||||
@@ -840,9 +800,6 @@ type XDNSDomain struct {
|
|||||||
|
|
||||||
type XDNSResolver struct {
|
type XDNSResolver struct {
|
||||||
Addrs []string `json:"addrs"`
|
Addrs []string `json:"addrs"`
|
||||||
Worker int32 `json:"worker"`
|
|
||||||
TlsConfig *TLSConfig `json:"tlsConfig"`
|
|
||||||
QuicConfig *QuicParamsConfig `json:"quicConfig"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type XDNS struct {
|
type XDNS struct {
|
||||||
@@ -877,26 +834,6 @@ func (c *XDNS) Build() (proto.Message, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i := range c.Resolvers {
|
for i := range c.Resolvers {
|
||||||
worker := c.Resolvers[i].Worker
|
|
||||||
if worker == 0 {
|
|
||||||
worker = 32
|
|
||||||
}
|
|
||||||
var tlsConfig *tls.Config
|
|
||||||
var quicConfig *internet.QuicParams
|
|
||||||
if c.Resolvers[i].TlsConfig != nil {
|
|
||||||
proto, err := c.Resolvers[i].TlsConfig.Build()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
tlsConfig = proto.(*tls.Config)
|
|
||||||
}
|
|
||||||
if c.Resolvers[i].QuicConfig != nil {
|
|
||||||
proto, err := c.Resolvers[i].QuicConfig.Build()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
quicConfig = proto.(*internet.QuicParams)
|
|
||||||
}
|
|
||||||
for j := range c.Resolvers[i].Addrs {
|
for j := range c.Resolvers[i].Addrs {
|
||||||
var u *url.URL
|
var u *url.URL
|
||||||
var e error
|
var e error
|
||||||
@@ -908,21 +845,18 @@ func (c *XDNS) Build() (proto.Message, error) {
|
|||||||
if e != nil {
|
if e != nil {
|
||||||
return nil, e
|
return nil, e
|
||||||
}
|
}
|
||||||
var addr string
|
|
||||||
switch u.Scheme {
|
switch u.Scheme {
|
||||||
case "tcp", "udp":
|
case "tcp", "udp":
|
||||||
host := u.Hostname()
|
|
||||||
port := u.Port()
|
|
||||||
if port == "" {
|
|
||||||
port = "53"
|
|
||||||
}
|
|
||||||
addr = net.JoinHostPort(host, port)
|
|
||||||
case "https":
|
|
||||||
addr = u.String()
|
|
||||||
default:
|
default:
|
||||||
return nil, errors.New("invalid protocol")
|
return nil, errors.New("invalid protocol")
|
||||||
}
|
}
|
||||||
resolvers = append(resolvers, &xdns.ResolverProto{Type: u.Scheme, Addr: addr, Worker: worker, TlsConfig: tlsConfig, QuicConfig: quicConfig})
|
var host, port string
|
||||||
|
host = u.Hostname()
|
||||||
|
port = u.Port()
|
||||||
|
if port == "" {
|
||||||
|
port = "53"
|
||||||
|
}
|
||||||
|
resolvers = append(resolvers, &xdns.ResolverProto{Type: u.Scheme, Addr: net.JoinHostPort(host, port)})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if c.ExtraPoll < 0 || c.ExtraPoll > 3 {
|
if c.ExtraPoll < 0 || c.ExtraPoll > 3 {
|
||||||
@@ -1215,91 +1149,6 @@ type QuicParamsConfig struct {
|
|||||||
DisableStatelessReset bool `json:"disableStatelessReset"`
|
DisableStatelessReset bool `json:"disableStatelessReset"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (config *QuicParamsConfig) Build() (proto.Message, error) {
|
|
||||||
profile := strings.ToLower(config.BbrProfile)
|
|
||||||
switch profile {
|
|
||||||
case "", string(bbr.ProfileConservative), string(bbr.ProfileStandard), string(bbr.ProfileAggressive):
|
|
||||||
if profile == "" {
|
|
||||||
profile = string(bbr.ProfileStandard)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return nil, errors.New("unknown bbr profile")
|
|
||||||
}
|
|
||||||
|
|
||||||
up, err := config.BrutalUp.Bps()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
down, err := config.BrutalDown.Bps()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if up > 0 && up < 65536 {
|
|
||||||
return nil, errors.New("BrutalUp must be at least 65536 bytes per second")
|
|
||||||
}
|
|
||||||
if down > 0 && down < 65536 {
|
|
||||||
return nil, errors.New("BrutalDown must be at least 65536 bytes per second")
|
|
||||||
}
|
|
||||||
|
|
||||||
config.Congestion = strings.ToLower(config.Congestion)
|
|
||||||
switch config.Congestion {
|
|
||||||
case "", "brutal", "reno", "bbr":
|
|
||||||
case "force-brutal":
|
|
||||||
if up == 0 {
|
|
||||||
return nil, errors.New("force-brutal requires up")
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return nil, errors.New("unknown congestion control: ", config.Congestion, ", valid values: reno, bbr, brutal, force-brutal")
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.InitStreamReceiveWindow > 0 && config.InitStreamReceiveWindow < 16384 {
|
|
||||||
return nil, errors.New("InitStreamReceiveWindow must be at least 16384")
|
|
||||||
}
|
|
||||||
if config.MaxStreamReceiveWindow > 0 && config.MaxStreamReceiveWindow < 16384 {
|
|
||||||
return nil, errors.New("MaxStreamReceiveWindow must be at least 16384")
|
|
||||||
}
|
|
||||||
if config.InitConnectionReceiveWindow > 0 && config.InitConnectionReceiveWindow < 16384 {
|
|
||||||
return nil, errors.New("InitConnectionReceiveWindow must be at least 16384")
|
|
||||||
}
|
|
||||||
if config.MaxConnectionReceiveWindow > 0 && config.MaxConnectionReceiveWindow < 16384 {
|
|
||||||
return nil, errors.New("MaxConnectionReceiveWindow must be at least 16384")
|
|
||||||
}
|
|
||||||
if config.MaxIdleTimeout != 0 && (config.MaxIdleTimeout < 4 || config.MaxIdleTimeout > 120) {
|
|
||||||
return nil, errors.New("MaxIdleTimeout must be between 4 and 120")
|
|
||||||
}
|
|
||||||
if config.KeepAlivePeriod != 0 && (config.KeepAlivePeriod < 2 || config.KeepAlivePeriod > 60) {
|
|
||||||
return nil, errors.New("KeepAlivePeriod must be between 2 and 60")
|
|
||||||
}
|
|
||||||
if config.MaxIncomingStreams != 0 && config.MaxIncomingStreams < 8 {
|
|
||||||
return nil, errors.New("MaxIncomingStreams must be at least 8")
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.Debug {
|
|
||||||
os.Setenv("HYSTERIA_BBR_DEBUG", "true")
|
|
||||||
os.Setenv("HYSTERIA_BRUTAL_DEBUG", "true")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &internet.QuicParams{
|
|
||||||
Congestion: config.Congestion,
|
|
||||||
BbrProfile: profile,
|
|
||||||
BrutalUp: up,
|
|
||||||
BrutalDown: down,
|
|
||||||
BrutalDisableLossCompensation: config.BrutalDisableLossCompensation,
|
|
||||||
InitStreamReceiveWindow: config.InitStreamReceiveWindow,
|
|
||||||
MaxStreamReceiveWindow: config.MaxStreamReceiveWindow,
|
|
||||||
InitConnReceiveWindow: config.InitConnectionReceiveWindow,
|
|
||||||
MaxConnReceiveWindow: config.MaxConnectionReceiveWindow,
|
|
||||||
MaxIdleTimeout: config.MaxIdleTimeout,
|
|
||||||
KeepAlivePeriod: config.KeepAlivePeriod,
|
|
||||||
DisablePathMtuDiscovery: config.DisablePathMTUDiscovery,
|
|
||||||
DisableChromeParrot: config.DisableChromeParrot,
|
|
||||||
DisableGSO: config.DisableGSO,
|
|
||||||
MaxIncomingStreams: config.MaxIncomingStreams,
|
|
||||||
DisableStatelessReset: config.DisableStatelessReset,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type FinalMask struct {
|
type FinalMask struct {
|
||||||
Tcp []Mask `json:"tcp"`
|
Tcp []Mask `json:"tcp"`
|
||||||
Udp []Mask `json:"udp"`
|
Udp []Mask `json:"udp"`
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
package conf_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/geodata"
|
|
||||||
. "github.com/xtls/xray-core/infra/conf"
|
|
||||||
"github.com/xtls/xray-core/transport/internet"
|
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask/filter"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFinalMaskFilter(t *testing.T) {
|
|
||||||
for _, network := range []string{"tcp", "udp"} {
|
|
||||||
for _, test := range []struct {
|
|
||||||
name, settings string
|
|
||||||
blocked, allowed int
|
|
||||||
}{
|
|
||||||
{"blacklist", `{"blockedSourceIPs":["192.0.2.0/24"]}`, 1, 0},
|
|
||||||
{"whitelist", `{"allowedSourceIPs":["192.0.2.0/24"]}`, 0, 1},
|
|
||||||
{"both", `{"blockedSourceIPs":["192.0.2.0/24"],"allowedSourceIPs":["192.0.2.0/24"]}`, 1, 1},
|
|
||||||
{"empty_whitelist", `{"allowedSourceIPs":[]}`, 0, 0},
|
|
||||||
{"empty_blacklist", `{"blockedSourceIPs":[]}`, 0, 0},
|
|
||||||
} {
|
|
||||||
t.Run(network+"/"+test.name, func(t *testing.T) {
|
|
||||||
input := fmt.Sprintf(`{"finalmask":{"%s":[{"type":"filter","settings":%s}]}}`, network, test.settings)
|
|
||||||
var config StreamConfig
|
|
||||||
if err := json.Unmarshal([]byte(input), &config); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
built, err := config.Build()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
masks := built.Tcpmasks
|
|
||||||
if network == "udp" {
|
|
||||||
masks = built.Udpmasks
|
|
||||||
}
|
|
||||||
if len(masks) != 1 {
|
|
||||||
t.Fatalf("expected one mask, got %d", len(masks))
|
|
||||||
}
|
|
||||||
instance, err := masks[0].GetInstance()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
filter, ok := instance.(*filter.Config)
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("unexpected mask type: %T", instance)
|
|
||||||
}
|
|
||||||
if len(filter.BlockedSourceIps) != test.blocked || len(filter.AllowedSourceIps) != test.allowed {
|
|
||||||
t.Fatalf("unexpected filter policy: %v", filter)
|
|
||||||
}
|
|
||||||
for _, rules := range [][]*geodata.IPRule{filter.BlockedSourceIps, filter.AllowedSourceIps} {
|
|
||||||
for _, rule := range rules {
|
|
||||||
cidr := rule.GetCustom().GetCidr()
|
|
||||||
if cidr.GetPrefix() != 24 || !net.IP(cidr.GetIp()).Equal(net.IPv4(192, 0, 2, 0)) {
|
|
||||||
t.Fatalf("unexpected IP rule: %v", cidr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if _, err := internet.ToMemoryStreamConfig(built); err != nil {
|
|
||||||
t.Fatalf("failed to initialize runtime finalmask: %v", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, field := range []string{"blockedSourceIPs", "allowedSourceIPs"} {
|
|
||||||
var config Filter
|
|
||||||
if err := json.Unmarshal([]byte(fmt.Sprintf(`{"%s":["192.0.2.0/33"]}`, field)), &config); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if _, err := config.Build(); err == nil || !strings.Contains(err.Error(), field) {
|
|
||||||
t.Errorf("expected invalid %s, got %v", field, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,9 +2,6 @@ package conf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask/noise"
|
"github.com/xtls/xray-core/transport/internet/finalmask/noise"
|
||||||
@@ -137,68 +134,3 @@ func TestNoiseExpFromJSON(t *testing.T) {
|
|||||||
t.Errorf("item 3 = %+v", items[3])
|
t.Errorf("item 3 = %+v", items[3])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const noiseScript = `function HandleNoise(plan, address) plan:Add("n") end
|
|
||||||
`
|
|
||||||
|
|
||||||
func writeNoiseScript(t *testing.T, script string) string {
|
|
||||||
t.Helper()
|
|
||||||
path := filepath.Join(t.TempDir(), "noise.lua")
|
|
||||||
if err := os.WriteFile(path, []byte(script), 0o600); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildNoiseMask(t *testing.T, mask *NoiseMask) *noise.Config {
|
|
||||||
t.Helper()
|
|
||||||
msg, err := mask.Build()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
return msg.(*noise.Config)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNoiseScriptFromFile(t *testing.T) {
|
|
||||||
path := writeNoiseScript(t, noiseScript)
|
|
||||||
cfg := buildNoiseMask(t, &NoiseMask{Script: path, Reset: Int32Range{From: 30, To: 60}})
|
|
||||||
|
|
||||||
if cfg.Script != path {
|
|
||||||
t.Errorf("got script %q, want the path", cfg.Script)
|
|
||||||
}
|
|
||||||
if len(cfg.Items) != 0 {
|
|
||||||
t.Errorf("got %d items, want none", len(cfg.Items))
|
|
||||||
}
|
|
||||||
if cfg.ResetMin != 30 || cfg.ResetMax != 60 {
|
|
||||||
t.Errorf("got reset %d-%d, want 30-60", cfg.ResetMin, cfg.ResetMax)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNoiseScriptFromJSON(t *testing.T) {
|
|
||||||
path := writeNoiseScript(t, noiseScript)
|
|
||||||
input := `{"script": ` + strconv.Quote(path) + `, "reset": "30-60"}`
|
|
||||||
var mask NoiseMask
|
|
||||||
if err := json.Unmarshal([]byte(input), &mask); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if cfg := buildNoiseMask(t, &mask); cfg.Script != path {
|
|
||||||
t.Errorf("got script %q, want the path", cfg.Script)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNoiseScriptRejected(t *testing.T) {
|
|
||||||
path := writeNoiseScript(t, noiseScript)
|
|
||||||
invalid := writeNoiseScript(t, "function HandleNoise(")
|
|
||||||
|
|
||||||
for name, mask := range map[string]*NoiseMask{
|
|
||||||
"script with noise": {Script: path, Noise: []NoiseItem{{Rand: Int32Range{From: 10, To: 20}}}},
|
|
||||||
"missing file": {Script: filepath.Join(t.TempDir(), "absent.lua")},
|
|
||||||
"not lua": {Script: invalid},
|
|
||||||
} {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
if _, err := mask.Build(); err == nil {
|
|
||||||
t.Error("expected an error")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
package conf
|
package conf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"os"
|
||||||
"slices"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
utls "github.com/refraction-networking/utls"
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/serial"
|
"github.com/xtls/xray-core/common/serial"
|
||||||
"github.com/xtls/xray-core/transport/internet"
|
"github.com/xtls/xray-core/transport/internet"
|
||||||
"github.com/xtls/xray-core/transport/internet/tls"
|
"github.com/xtls/xray-core/transport/internet/hysteria/congestion/bbr"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TransportProtocol string
|
type TransportProtocol string
|
||||||
@@ -90,7 +88,6 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
|
|||||||
config.ProtocolName = protocol
|
config.ProtocolName = protocol
|
||||||
}
|
}
|
||||||
|
|
||||||
var xdriveH3 bool
|
|
||||||
switch strings.ToLower(c.Security) {
|
switch strings.ToLower(c.Security) {
|
||||||
case "", "none":
|
case "", "none":
|
||||||
case "tls":
|
case "tls":
|
||||||
@@ -105,15 +102,6 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
|
|||||||
tm := serial.ToTypedMessage(ts)
|
tm := serial.ToTypedMessage(ts)
|
||||||
config.SecuritySettings = append(config.SecuritySettings, tm)
|
config.SecuritySettings = append(config.SecuritySettings, tm)
|
||||||
config.SecurityType = tm.Type
|
config.SecurityType = tm.Type
|
||||||
xdriveH3 = config.ProtocolName == "xdrive" && tlsSettings.ALPN != nil && slices.Equal(*tlsSettings.ALPN, StringList{"h3"})
|
|
||||||
if config.ProtocolName == "xdrive" && tlsSettings.ALPN != nil && slices.Equal(*tlsSettings.ALPN, StringList{"http/1.1"}) && tlsSettings.ECHConfigList == "" {
|
|
||||||
// a fingerprint puts its own ALPN into the hello, except Go TLS and the
|
|
||||||
// randomized ones, which take it from the config or send none
|
|
||||||
if fp := tls.GetFingerprint(strings.ToLower(tlsSettings.Fingerprint)); fp != nil && fp.Client != utls.HelloGolang.Client &&
|
|
||||||
!strings.HasPrefix(fp.Client, utls.HelloRandomized.Client) {
|
|
||||||
errors.LogWarning(context.Background(), `XDRIVE: "alpn" ["http/1.1"] reaches the wire only inside ECH, without "echConfigList" the hello carries the ALPN of the fingerprint and the server may pick h2`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "reality":
|
case "reality":
|
||||||
if config.ProtocolName != "tcp" && config.ProtocolName != "splithttp" && config.ProtocolName != "grpc" {
|
if config.ProtocolName != "tcp" && config.ProtocolName != "splithttp" && config.ProtocolName != "grpc" {
|
||||||
return nil, errors.New("REALITY only supports RAW, XHTTP and gRPC for now.")
|
return nil, errors.New("REALITY only supports RAW, XHTTP and gRPC for now.")
|
||||||
@@ -221,7 +209,6 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
if c.XDRIVESettings != nil {
|
if c.XDRIVESettings != nil {
|
||||||
c.XDRIVESettings.h3 = xdriveH3
|
|
||||||
xs, err := c.XDRIVESettings.Build()
|
xs, err := c.XDRIVESettings.Build()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("Failed to build XDRIVE config.").Base(err)
|
return nil, errors.New("Failed to build XDRIVE config.").Base(err)
|
||||||
@@ -241,40 +228,102 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
|
|||||||
|
|
||||||
if c.FinalMask != nil {
|
if c.FinalMask != nil {
|
||||||
for _, mask := range c.FinalMask.Tcp {
|
for _, mask := range c.FinalMask.Tcp {
|
||||||
proto, err := mask.Build(true)
|
u, err := mask.Build(true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("failed to build mask with type ", mask.Type).Base(err)
|
return nil, errors.New("failed to build mask with type ", mask.Type).Base(err)
|
||||||
}
|
}
|
||||||
config.Tcpmasks = append(config.Tcpmasks, serial.ToTypedMessage(proto))
|
config.Tcpmasks = append(config.Tcpmasks, serial.ToTypedMessage(u))
|
||||||
}
|
}
|
||||||
for _, mask := range c.FinalMask.Udp {
|
for _, mask := range c.FinalMask.Udp {
|
||||||
proto, err := mask.Build(false)
|
u, err := mask.Build(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("failed to build mask with type ", mask.Type).Base(err)
|
return nil, errors.New("failed to build mask with type ", mask.Type).Base(err)
|
||||||
}
|
}
|
||||||
config.Udpmasks = append(config.Udpmasks, serial.ToTypedMessage(proto))
|
config.Udpmasks = append(config.Udpmasks, serial.ToTypedMessage(u))
|
||||||
}
|
}
|
||||||
if c.FinalMask.QuicParams != nil {
|
if c.FinalMask.QuicParams != nil {
|
||||||
proto, err := c.FinalMask.QuicParams.Build()
|
profile := strings.ToLower(c.FinalMask.QuicParams.BbrProfile)
|
||||||
|
switch profile {
|
||||||
|
case "", string(bbr.ProfileConservative), string(bbr.ProfileStandard), string(bbr.ProfileAggressive):
|
||||||
|
if profile == "" {
|
||||||
|
profile = string(bbr.ProfileStandard)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, errors.New("unknown bbr profile")
|
||||||
|
}
|
||||||
|
|
||||||
|
up, err := c.FinalMask.QuicParams.BrutalUp.Bps()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
config.QuicParams = proto.(*internet.QuicParams)
|
down, err := c.FinalMask.QuicParams.BrutalDown.Bps()
|
||||||
}
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if xdriveH3 && c.FinalMask != nil {
|
if up > 0 && up < 65536 {
|
||||||
if c.XDRIVESettings != nil && c.XDRIVESettings.Service == "google-drive" {
|
return nil, errors.New("BrutalUp must be at least 65536 bytes per second")
|
||||||
// noise only sends datagrams of its own ahead of the QUIC ones, the
|
|
||||||
// other masks change those or need a peer that undoes them
|
|
||||||
for _, mask := range c.FinalMask.Udp {
|
|
||||||
if strings.ToLower(mask.Type) != "noise" {
|
|
||||||
return nil, errors.New(`XDRIVE: over h3 to Google Drive the only "udp" mask that works is "noise", not "`, mask.Type, `"`)
|
|
||||||
}
|
}
|
||||||
|
if down > 0 && down < 65536 {
|
||||||
|
return nil, errors.New("BrutalDown must be at least 65536 bytes per second")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.FinalMask.QuicParams.Congestion = strings.ToLower(c.FinalMask.QuicParams.Congestion)
|
||||||
|
switch c.FinalMask.QuicParams.Congestion {
|
||||||
|
case "", "brutal", "reno", "bbr":
|
||||||
|
case "force-brutal":
|
||||||
|
if up == 0 {
|
||||||
|
return nil, errors.New("force-brutal requires up")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, errors.New("unknown congestion control: ", c.FinalMask.QuicParams.Congestion, ", valid values: reno, bbr, brutal, force-brutal")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.FinalMask.QuicParams.InitStreamReceiveWindow > 0 && c.FinalMask.QuicParams.InitStreamReceiveWindow < 16384 {
|
||||||
|
return nil, errors.New("InitStreamReceiveWindow must be at least 16384")
|
||||||
|
}
|
||||||
|
if c.FinalMask.QuicParams.MaxStreamReceiveWindow > 0 && c.FinalMask.QuicParams.MaxStreamReceiveWindow < 16384 {
|
||||||
|
return nil, errors.New("MaxStreamReceiveWindow must be at least 16384")
|
||||||
|
}
|
||||||
|
if c.FinalMask.QuicParams.InitConnectionReceiveWindow > 0 && c.FinalMask.QuicParams.InitConnectionReceiveWindow < 16384 {
|
||||||
|
return nil, errors.New("InitConnectionReceiveWindow must be at least 16384")
|
||||||
|
}
|
||||||
|
if c.FinalMask.QuicParams.MaxConnectionReceiveWindow > 0 && c.FinalMask.QuicParams.MaxConnectionReceiveWindow < 16384 {
|
||||||
|
return nil, errors.New("MaxConnectionReceiveWindow must be at least 16384")
|
||||||
|
}
|
||||||
|
if c.FinalMask.QuicParams.MaxIdleTimeout != 0 && (c.FinalMask.QuicParams.MaxIdleTimeout < 4 || c.FinalMask.QuicParams.MaxIdleTimeout > 120) {
|
||||||
|
return nil, errors.New("MaxIdleTimeout must be between 4 and 120")
|
||||||
|
}
|
||||||
|
if c.FinalMask.QuicParams.KeepAlivePeriod != 0 && (c.FinalMask.QuicParams.KeepAlivePeriod < 2 || c.FinalMask.QuicParams.KeepAlivePeriod > 60) {
|
||||||
|
return nil, errors.New("KeepAlivePeriod must be between 2 and 60")
|
||||||
|
}
|
||||||
|
if c.FinalMask.QuicParams.MaxIncomingStreams != 0 && c.FinalMask.QuicParams.MaxIncomingStreams < 8 {
|
||||||
|
return nil, errors.New("MaxIncomingStreams must be at least 8")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.FinalMask.QuicParams.Debug {
|
||||||
|
os.Setenv("HYSTERIA_BBR_DEBUG", "true")
|
||||||
|
os.Setenv("HYSTERIA_BRUTAL_DEBUG", "true")
|
||||||
|
}
|
||||||
|
|
||||||
|
config.QuicParams = &internet.QuicParams{
|
||||||
|
Congestion: c.FinalMask.QuicParams.Congestion,
|
||||||
|
BbrProfile: profile,
|
||||||
|
BrutalUp: up,
|
||||||
|
BrutalDown: down,
|
||||||
|
BrutalDisableLossCompensation: c.FinalMask.QuicParams.BrutalDisableLossCompensation,
|
||||||
|
InitStreamReceiveWindow: c.FinalMask.QuicParams.InitStreamReceiveWindow,
|
||||||
|
MaxStreamReceiveWindow: c.FinalMask.QuicParams.MaxStreamReceiveWindow,
|
||||||
|
InitConnReceiveWindow: c.FinalMask.QuicParams.InitConnectionReceiveWindow,
|
||||||
|
MaxConnReceiveWindow: c.FinalMask.QuicParams.MaxConnectionReceiveWindow,
|
||||||
|
MaxIdleTimeout: c.FinalMask.QuicParams.MaxIdleTimeout,
|
||||||
|
KeepAlivePeriod: c.FinalMask.QuicParams.KeepAlivePeriod,
|
||||||
|
DisablePathMtuDiscovery: c.FinalMask.QuicParams.DisablePathMTUDiscovery,
|
||||||
|
DisableChromeParrot: c.FinalMask.QuicParams.DisableChromeParrot,
|
||||||
|
DisableGSO: c.FinalMask.QuicParams.DisableGSO,
|
||||||
|
MaxIncomingStreams: c.FinalMask.QuicParams.MaxIncomingStreams,
|
||||||
|
DisableStatelessReset: c.FinalMask.QuicParams.DisableStatelessReset,
|
||||||
}
|
}
|
||||||
if config.QuicParams != nil && (config.QuicParams.Congestion == "brutal" || config.QuicParams.Congestion == "force-brutal") {
|
|
||||||
return nil, errors.New(`XDRIVE: brutal is for Hysteria servers, over h3 "congestion" is "bbr" or "reno", not "`, config.QuicParams.Congestion, `"`)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -997,10 +997,6 @@ type XDriveConfig struct {
|
|||||||
EagerWindowMs uint32 `json:"eagerWindowMs"`
|
EagerWindowMs uint32 `json:"eagerWindowMs"`
|
||||||
HoleTimeoutMs uint32 `json:"holeTimeoutMs"`
|
HoleTimeoutMs uint32 `json:"holeTimeoutMs"`
|
||||||
Template json.RawMessage `json:"template"`
|
Template json.RawMessage `json:"template"`
|
||||||
Xmux XmuxConfig `json:"xmux"`
|
|
||||||
|
|
||||||
// set by StreamConfig when "alpn" is ["h3"]
|
|
||||||
h3 bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build implements Buildable.
|
// Build implements Buildable.
|
||||||
@@ -1008,10 +1004,6 @@ func (c *XDriveConfig) Build() (proto.Message, error) {
|
|||||||
switch c.Service {
|
switch c.Service {
|
||||||
case "local":
|
case "local":
|
||||||
case "Google Drive":
|
case "Google Drive":
|
||||||
errors.PrintDeprecatedFeatureWarning(`"service": "Google Drive"`, `"service": "google-drive"`)
|
|
||||||
c.Service = "google-drive"
|
|
||||||
fallthrough
|
|
||||||
case "google-drive":
|
|
||||||
if len(c.Secrets) != 3 {
|
if len(c.Secrets) != 3 {
|
||||||
return nil, errors.New("Google Drive needs 3 secrets in order of ClientID, ClientSecret, RefreshToken")
|
return nil, errors.New("Google Drive needs 3 secrets in order of ClientID, ClientSecret, RefreshToken")
|
||||||
}
|
}
|
||||||
@@ -1020,22 +1012,7 @@ func (c *XDriveConfig) Build() (proto.Message, error) {
|
|||||||
return nil, errors.New(`service "template" needs a "template" object`)
|
return nil, errors.New(`service "template" needs a "template" object`)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return nil, errors.New(`unknown "service": "`, c.Service, `", use "google-drive", "template" or "local"`)
|
return nil, errors.New("unsupported service")
|
||||||
}
|
|
||||||
if c.Xmux.MaxConnections.To > 0 && c.Xmux.MaxConcurrency.To > 0 {
|
|
||||||
return nil, errors.New("maxConnections cannot be specified together with maxConcurrency")
|
|
||||||
}
|
|
||||||
if c.Xmux == (XmuxConfig{}) {
|
|
||||||
c.Xmux.MaxConnections.From = 1
|
|
||||||
c.Xmux.MaxConnections.To = 1
|
|
||||||
if c.Service == "google-drive" && !c.h3 {
|
|
||||||
// Google gives an h2 connection 1 MiB of window, which a few sessions on
|
|
||||||
// a long rtt would share, an h3 connection has no such limit
|
|
||||||
c.Xmux.MaxConnections.From = 4
|
|
||||||
c.Xmux.MaxConnections.To = 4
|
|
||||||
}
|
|
||||||
c.Xmux.HMaxReusableSecs.From = 1800
|
|
||||||
c.Xmux.HMaxReusableSecs.To = 3000
|
|
||||||
}
|
}
|
||||||
config := &xdrive.Config{
|
config := &xdrive.Config{
|
||||||
RemoteFolder: c.RemoteFolder,
|
RemoteFolder: c.RemoteFolder,
|
||||||
@@ -1050,14 +1027,6 @@ func (c *XDriveConfig) Build() (proto.Message, error) {
|
|||||||
EagerWindowMs: c.EagerWindowMs,
|
EagerWindowMs: c.EagerWindowMs,
|
||||||
HoleTimeoutMs: c.HoleTimeoutMs,
|
HoleTimeoutMs: c.HoleTimeoutMs,
|
||||||
Template: string(c.Template),
|
Template: string(c.Template),
|
||||||
Xmux: &splithttp.XmuxConfig{
|
|
||||||
MaxConcurrency: newRangeConfig(c.Xmux.MaxConcurrency),
|
|
||||||
MaxConnections: newRangeConfig(c.Xmux.MaxConnections),
|
|
||||||
CMaxReuseTimes: newRangeConfig(c.Xmux.CMaxReuseTimes),
|
|
||||||
HMaxRequestTimes: newRangeConfig(c.Xmux.HMaxRequestTimes),
|
|
||||||
HMaxReusableSecs: newRangeConfig(c.Xmux.HMaxReusableSecs),
|
|
||||||
HKeepAlivePeriod: c.Xmux.HKeepAlivePeriod,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,12 +5,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/log"
|
|
||||||
. "github.com/xtls/xray-core/infra/conf"
|
. "github.com/xtls/xray-core/infra/conf"
|
||||||
"github.com/xtls/xray-core/transport/internet"
|
"github.com/xtls/xray-core/transport/internet"
|
||||||
finalmaskcustom "github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
|
finalmaskcustom "github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
|
||||||
"github.com/xtls/xray-core/transport/internet/splithttp"
|
|
||||||
"github.com/xtls/xray-core/transport/internet/xdrive"
|
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -324,25 +321,8 @@ func TestXDriveRejectsUnknownService(t *testing.T) {
|
|||||||
if err := json.Unmarshal([]byte(`{"remoteFolder": "/tmp/xdrive", "service": "Dropbox"}`), config); err != nil {
|
if err := json.Unmarshal([]byte(`{"remoteFolder": "/tmp/xdrive", "service": "Dropbox"}`), config); err != nil {
|
||||||
t.Fatalf("Unmarshal: %v", err)
|
t.Fatalf("Unmarshal: %v", err)
|
||||||
}
|
}
|
||||||
if _, err := config.Build(); err == nil || !strings.Contains(err.Error(), `use "google-drive", "template" or "local"`) {
|
|
||||||
t.Fatalf("expected an unknown service error, got %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestXDriveGoogleDriveName(t *testing.T) {
|
|
||||||
for _, service := range []string{"google-drive", "Google Drive"} {
|
|
||||||
config := &XDriveConfig{RemoteFolder: "folder-id", Service: service, Secrets: []string{"a", "b", "c"}}
|
|
||||||
built, err := config.Build()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%s: %v", service, err)
|
|
||||||
}
|
|
||||||
if got := built.(*xdrive.Config).Service; got != "google-drive" {
|
|
||||||
t.Errorf("%s: service is %q, want google-drive", service, got)
|
|
||||||
}
|
|
||||||
config = &XDriveConfig{RemoteFolder: "folder-id", Service: service, Secrets: []string{"a", "b"}}
|
|
||||||
if _, err := config.Build(); err == nil {
|
if _, err := config.Build(); err == nil {
|
||||||
t.Errorf("%s: accepted two secrets", service)
|
t.Fatal("Build accepted an unsupported service")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -375,131 +355,6 @@ func TestXDriveTemplateStreamConfig(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type logLines []string
|
|
||||||
|
|
||||||
func (l *logLines) Handle(msg log.Message) {
|
|
||||||
*l = append(*l, msg.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// "alpn" ["http/1.1"] is warned about where the hello does not carry it, which
|
|
||||||
// is outside ECH with a fingerprint that has an ALPN of its own
|
|
||||||
func TestXDriveHTTP1ALPNWarning(t *testing.T) {
|
|
||||||
var lines logLines
|
|
||||||
log.RegisterHandler(&lines)
|
|
||||||
|
|
||||||
for _, c := range []struct {
|
|
||||||
method, tlsSettings string
|
|
||||||
warn bool
|
|
||||||
}{
|
|
||||||
{"xdrive", `{}`, false},
|
|
||||||
{"xdrive", `{"alpn": ["http/1.1"]}`, true},
|
|
||||||
{"xdrive", `{"alpn": ["http/1.1"], "fingerprint": "Firefox"}`, true},
|
|
||||||
{"raw", `{"alpn": ["http/1.1"]}`, false},
|
|
||||||
{"xdrive", `{"alpn": ["h2", "http/1.1"]}`, false},
|
|
||||||
{"xdrive", `{"alpn": ["http/1.1"], "echConfigList": "udp://1.1.1.1"}`, false},
|
|
||||||
{"xdrive", `{"alpn": ["http/1.1"], "fingerprint": "unsafe"}`, false},
|
|
||||||
{"xdrive", `{"alpn": ["http/1.1"], "fingerprint": "hellogolang"}`, false},
|
|
||||||
{"xdrive", `{"alpn": ["http/1.1"], "fingerprint": "randomized"}`, false},
|
|
||||||
{"xdrive", `{"alpn": ["http/1.1"], "fingerprint": "hellorandomizednoalpn"}`, false},
|
|
||||||
} {
|
|
||||||
lines = nil
|
|
||||||
config := new(StreamConfig)
|
|
||||||
if err := json.Unmarshal([]byte(`{
|
|
||||||
"method": "`+c.method+`",
|
|
||||||
"security": "tls",
|
|
||||||
"tlsSettings": `+c.tlsSettings+`,
|
|
||||||
"xdriveSettings": {"remoteFolder": "/tmp/xdrive", "service": "local"}
|
|
||||||
}`), config); err != nil {
|
|
||||||
t.Fatalf("Unmarshal: %v", err)
|
|
||||||
}
|
|
||||||
if _, err := config.Build(); err != nil {
|
|
||||||
t.Fatalf("%s %s: %v", c.method, c.tlsSettings, err)
|
|
||||||
}
|
|
||||||
warned := false
|
|
||||||
for _, line := range lines {
|
|
||||||
warned = warned || strings.HasPrefix(line, "[Warning]") && strings.Contains(line, `"alpn"`)
|
|
||||||
}
|
|
||||||
if warned != c.warn {
|
|
||||||
t.Errorf("%s %s: warned %v, want %v", c.method, c.tlsSettings, warned, c.warn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// over h3 Google Drive takes only the noise mask, and no service takes brutal
|
|
||||||
func TestXDriveH3Checks(t *testing.T) {
|
|
||||||
const (
|
|
||||||
drive = `"method": "xdrive", "xdriveSettings": {"remoteFolder": "f", "service": "google-drive", "secrets": ["a", "b", "c"]}`
|
|
||||||
local = `"method": "xdrive", "xdriveSettings": {"remoteFolder": "/tmp/xdrive", "service": "local"}`
|
|
||||||
noise = `{"type": "Noise", "settings": {"noise": [{"rand": "50-100"}]}}`
|
|
||||||
salamander = `{"type": "salamander", "settings": {"password": "p"}}`
|
|
||||||
forceBrutal = `{"quicParams": {"congestion": "force-brutal", "brutalUp": "10 mbps"}}`
|
|
||||||
)
|
|
||||||
for _, c := range []struct {
|
|
||||||
alpn, transport, finalmask string
|
|
||||||
ok bool
|
|
||||||
}{
|
|
||||||
{`["h3"]`, drive, `{"udp": [` + noise + `]}`, true},
|
|
||||||
{`["h3"]`, drive, `{"udp": [` + salamander + `]}`, false},
|
|
||||||
{`["h3"]`, drive, `{"udp": [` + noise + `, ` + salamander + `]}`, false},
|
|
||||||
{`["h3"]`, local, `{"udp": [` + salamander + `]}`, true},
|
|
||||||
{`["h3"]`, `"method": "xdrive"`, `{"udp": [` + salamander + `]}`, true},
|
|
||||||
{`["h2"]`, drive, `{"udp": [` + salamander + `]}`, true},
|
|
||||||
{`null`, drive, `{"udp": [` + salamander + `]}`, true},
|
|
||||||
{`["h3"]`, drive, `null`, true},
|
|
||||||
{`["h3"]`, drive, `{"quicParams": {"congestion": "bbr"}}`, true},
|
|
||||||
{`["h3"]`, drive, `{"quicParams": {"congestion": "brutal"}}`, false},
|
|
||||||
{`["h3"]`, local, forceBrutal, false},
|
|
||||||
{`["h3", "h2"]`, drive, `{"quicParams": {"congestion": "brutal"}}`, true},
|
|
||||||
{`["h3"]`, `"method": "xhttp"`, forceBrutal, true},
|
|
||||||
} {
|
|
||||||
config := new(StreamConfig)
|
|
||||||
if err := json.Unmarshal([]byte(`{
|
|
||||||
`+c.transport+`,
|
|
||||||
"security": "tls",
|
|
||||||
"tlsSettings": {"alpn": `+c.alpn+`},
|
|
||||||
"finalmask": `+c.finalmask+`
|
|
||||||
}`), config); err != nil {
|
|
||||||
t.Fatalf("Unmarshal: %v", err)
|
|
||||||
}
|
|
||||||
if _, err := config.Build(); (err == nil) != c.ok {
|
|
||||||
t.Errorf("alpn %s, %s, finalmask %s: %v", c.alpn, c.transport, c.finalmask, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// one h3 connection is not held back by a window like h2, so h3 takes one by default
|
|
||||||
func TestXDriveH3Connections(t *testing.T) {
|
|
||||||
for _, c := range []struct {
|
|
||||||
alpn string
|
|
||||||
want int32
|
|
||||||
}{
|
|
||||||
{`["h3"]`, 1},
|
|
||||||
{`["h2"]`, 4},
|
|
||||||
{`null`, 4},
|
|
||||||
} {
|
|
||||||
config := new(StreamConfig)
|
|
||||||
if err := json.Unmarshal([]byte(`{
|
|
||||||
"method": "xdrive",
|
|
||||||
"xdriveSettings": {"remoteFolder": "f", "service": "google-drive", "secrets": ["a", "b", "c"]},
|
|
||||||
"security": "tls",
|
|
||||||
"tlsSettings": {"alpn": `+c.alpn+`}
|
|
||||||
}`), config); err != nil {
|
|
||||||
t.Fatalf("Unmarshal: %v", err)
|
|
||||||
}
|
|
||||||
built, err := config.Build()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("alpn %s: %v", c.alpn, err)
|
|
||||||
}
|
|
||||||
xs, err := built.TransportSettings[0].Settings.GetInstance()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("alpn %s: %v", c.alpn, err)
|
|
||||||
}
|
|
||||||
if got := xs.(*xdrive.Config).Xmux.MaxConnections; got.From != c.want || got.To != c.want {
|
|
||||||
t.Errorf("alpn %s: maxConnections is %d-%d, want %d", c.alpn, got.From, got.To, c.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestXDriveTemplateNeedsTemplate(t *testing.T) {
|
func TestXDriveTemplateNeedsTemplate(t *testing.T) {
|
||||||
config := new(XDriveConfig)
|
config := new(XDriveConfig)
|
||||||
if err := json.Unmarshal([]byte(`{"remoteFolder": "f", "service": "template"}`), config); err != nil {
|
if err := json.Unmarshal([]byte(`{"remoteFolder": "f", "service": "template"}`), config); err != nil {
|
||||||
@@ -509,130 +364,3 @@ func TestXDriveTemplateNeedsTemplate(t *testing.T) {
|
|||||||
t.Fatal("Build accepted a template service without a template")
|
t.Fatal("Build accepted a template service without a template")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
xdriveStream = `"streamSettings": {"network": "xdrive", "xdriveSettings": {"remoteFolder": "f", "service": "local"}}`
|
|
||||||
xdriveID = `"id": "27848739-7e62-4138-9fd3-098a63964b6b"`
|
|
||||||
xdriveServer = `"address": "127.0.0.1", "port": 443, ` + xdriveID
|
|
||||||
xdriveEnc = `"encryption": "mlkem768x25519plus.native.0rtt.XKbaq1BuzJKKKPxZbwgRpn-gYQbnhrYketgezASCL1I"`
|
|
||||||
xdriveDec = `"decryption": "mlkem768x25519plus.native.600s.UKfUEMDdjlEnycTMBP0a08acky8vRRKSrM1MRnaYk3s"`
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestXDriveOutboundConfig(t *testing.T) {
|
|
||||||
build := func(s string) error {
|
|
||||||
c := new(OutboundDetourConfig)
|
|
||||||
if err := json.Unmarshal([]byte(s), c); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err := c.Build()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, input := range []string{
|
|
||||||
`{"protocol": "vless", "settings": {` + xdriveServer + `, ` + xdriveEnc + `}, ` + xdriveStream + `, "mux": {"enabled": true}}`,
|
|
||||||
`{"protocol": "vless", "settings": {` + xdriveServer + `, ` + xdriveEnc + `}, ` + xdriveStream + `, "mux": {"enabled": true, "concurrency": 0, "xudpConcurrency": 8, "xudpProxyUDP443": "allow"}}`,
|
|
||||||
`{"protocol": "vless", "settings": {` + xdriveServer + `, ` + xdriveEnc + `, "reverse": {"tag": "r"}}, ` + xdriveStream + `}`,
|
|
||||||
`{"protocol": "vless", "settings": {"vnext": [{"address": "127.0.0.1", "port": 443, "users": [{` + xdriveID + `, ` + xdriveEnc + `}]}]}, ` + xdriveStream + `, "mux": {"enabled": true}}`,
|
|
||||||
} {
|
|
||||||
if err := build(input); err != nil {
|
|
||||||
t.Errorf("%s: %v", input, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, c := range []struct{ input, err string }{
|
|
||||||
{`{"protocol": "vless", "settings": {` + xdriveServer + `, "encryption": "none"}, ` + xdriveStream + `, "mux": {"enabled": true}}`, `"encryption"`},
|
|
||||||
{`{"protocol": "vless", "settings": {"address": "example.com", "port": 443, ` + xdriveID + `, "encryption": "none"}, ` + xdriveStream + `, "mux": {"enabled": true}}`, `"encryption"`},
|
|
||||||
{`{"protocol": "vless", "settings": {` + xdriveServer + `, ` + xdriveEnc + `}, ` + xdriveStream + `}`, `"mux"`},
|
|
||||||
{`{"protocol": "vless", "settings": {` + xdriveServer + `, ` + xdriveEnc + `}, ` + xdriveStream + `, "mux": {"enabled": false}}`, `"mux"`},
|
|
||||||
{`{"protocol": "vless", "settings": {` + xdriveServer + `, ` + xdriveEnc + `}, ` + xdriveStream + `, "mux": {"enabled": true, "concurrency": -1}}`, `"mux"`},
|
|
||||||
{`{"protocol": "vless", "settings": {` + xdriveServer + `, ` + xdriveEnc + `}, ` + xdriveStream + `, "mux": {"enabled": true, "xudpConcurrency": -1}}`, `"mux"`},
|
|
||||||
{`{"protocol": "vless", "settings": {` + xdriveServer + `, ` + xdriveEnc + `}, ` + xdriveStream + `, "mux": {"enabled": true, "xudpProxyUDP443": "skip"}}`, `"mux"`},
|
|
||||||
{`{"protocol": "vless", "settings": {` + xdriveServer + `, ` + xdriveEnc + `, "flow": "xtls-rprx-vision"}, ` + xdriveStream + `, "mux": {"enabled": true, "concurrency": -1}}`, `"flow"`},
|
|
||||||
{`{"protocol": "vless", "settings": {"vnext": [{"address": "127.0.0.1", "port": 443, "users": [{` + xdriveID + `, ` + xdriveEnc + `, "flow": "xtls-rprx-vision"}]}]}, ` + xdriveStream + `, "mux": {"enabled": true, "concurrency": -1}}`, `"flow"`},
|
|
||||||
{`{"protocol": "vless", "settings": {` + xdriveServer + `, ` + xdriveEnc + `, "testpre": 1}, ` + xdriveStream + `, "mux": {"enabled": true}}`, `"testpre"`},
|
|
||||||
{`{"protocol": "vmess", "settings": {` + xdriveServer + `}, ` + xdriveStream + `, "mux": {"enabled": true}}`, "VLESS"},
|
|
||||||
{`{"protocol": "trojan", "settings": {"address": "example.com", "port": 443, "password": "p"}, ` + xdriveStream + `, "mux": {"enabled": true}}`, "VLESS"},
|
|
||||||
{`{"protocol": "freedom", ` + xdriveStream + `}`, "VLESS"},
|
|
||||||
} {
|
|
||||||
if err := build(c.input); err == nil || !strings.Contains(err.Error(), "xdrive transport") || !strings.Contains(err.Error(), c.err) {
|
|
||||||
t.Errorf("expected an xdrive error about %s for %s, got %v", c.err, c.input, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestXDriveInboundConfig(t *testing.T) {
|
|
||||||
build := func(s string) error {
|
|
||||||
c := new(InboundDetourConfig)
|
|
||||||
if err := json.Unmarshal([]byte(s), c); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err := c.Build()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, input := range []string{
|
|
||||||
`{"protocol": "vless", "port": 443, "settings": {"clients": [{` + xdriveID + `}], ` + xdriveDec + `}, ` + xdriveStream + `}`,
|
|
||||||
`{"protocol": "vless", "listen": "@xdrive", "settings": {"clients": [{` + xdriveID + `}], ` + xdriveDec + `}, ` + xdriveStream + `}`,
|
|
||||||
} {
|
|
||||||
if err := build(input); err != nil {
|
|
||||||
t.Errorf("%s: %v", input, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, c := range []struct{ input, err string }{
|
|
||||||
{`{"protocol": "vless", "port": 443, "settings": {"clients": [{` + xdriveID + `}], "decryption": "none"}, ` + xdriveStream + `}`, `"decryption"`},
|
|
||||||
{`{"protocol": "vless", "port": 443, "settings": {"clients": [{` + xdriveID + `}], ` + xdriveDec + `, "flow": "xtls-rprx-vision"}, ` + xdriveStream + `}`, `"flow"`},
|
|
||||||
{`{"protocol": "vless", "port": 443, "settings": {"clients": [{` + xdriveID + `, "flow": "xtls-rprx-vision"}], ` + xdriveDec + `}, ` + xdriveStream + `}`, `"flow"`},
|
|
||||||
{`{"protocol": "vmess", "port": 443, "settings": {"clients": [{` + xdriveID + `}]}, ` + xdriveStream + `}`, "VLESS"},
|
|
||||||
{`{"protocol": "vless", "port": "10800-10803", "settings": {"clients": [{` + xdriveID + `}], ` + xdriveDec + `}, ` + xdriveStream + `}`, "one port"},
|
|
||||||
} {
|
|
||||||
if err := build(c.input); err == nil || !strings.Contains(err.Error(), "xdrive transport") || !strings.Contains(err.Error(), c.err) {
|
|
||||||
t.Errorf("expected an xdrive error about %s for %s, got %v", c.err, c.input, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestXDriveXmux(t *testing.T) {
|
|
||||||
defaults := func(connections int32) *splithttp.XmuxConfig {
|
|
||||||
return &splithttp.XmuxConfig{
|
|
||||||
MaxConcurrency: &splithttp.RangeConfig{},
|
|
||||||
MaxConnections: &splithttp.RangeConfig{From: connections, To: connections},
|
|
||||||
CMaxReuseTimes: &splithttp.RangeConfig{},
|
|
||||||
HMaxRequestTimes: &splithttp.RangeConfig{},
|
|
||||||
HMaxReusableSecs: &splithttp.RangeConfig{From: 1800, To: 3000},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, c := range []struct {
|
|
||||||
input string
|
|
||||||
want *splithttp.XmuxConfig
|
|
||||||
}{
|
|
||||||
{`{"service": "google-drive", "secrets": ["a", "b", "c"]}`, defaults(4)},
|
|
||||||
{`{"service": "template", "template": {}}`, defaults(1)},
|
|
||||||
{`{"service": "google-drive", "secrets": ["a", "b", "c"], "xmux": {"maxConcurrency": "1-2", "hMaxRequestTimes": 100, "hKeepAlivePeriod": -1}}`, &splithttp.XmuxConfig{
|
|
||||||
MaxConcurrency: &splithttp.RangeConfig{From: 1, To: 2},
|
|
||||||
MaxConnections: &splithttp.RangeConfig{},
|
|
||||||
CMaxReuseTimes: &splithttp.RangeConfig{},
|
|
||||||
HMaxRequestTimes: &splithttp.RangeConfig{From: 100, To: 100},
|
|
||||||
HMaxReusableSecs: &splithttp.RangeConfig{},
|
|
||||||
HKeepAlivePeriod: -1,
|
|
||||||
}},
|
|
||||||
} {
|
|
||||||
config := new(XDriveConfig)
|
|
||||||
if err := json.Unmarshal([]byte(c.input), config); err != nil {
|
|
||||||
t.Fatalf("%s: Unmarshal: %v", c.input, err)
|
|
||||||
}
|
|
||||||
built, err := config.Build()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%s: Build: %v", c.input, err)
|
|
||||||
}
|
|
||||||
if got := built.(*xdrive.Config).Xmux; !proto.Equal(got, c.want) {
|
|
||||||
t.Errorf("%s: xmux is %v, want %v", c.input, got, c.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
config := new(XDriveConfig)
|
|
||||||
if err := json.Unmarshal([]byte(`{"service": "template", "template": {}, "xmux": {"maxConnections": 2, "maxConcurrency": 8}}`), config); err != nil {
|
|
||||||
t.Fatalf("Unmarshal: %v", err)
|
|
||||||
}
|
|
||||||
if _, err := config.Build(); err == nil {
|
|
||||||
t.Error("Build accepted maxConnections with maxConcurrency")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -17,11 +17,7 @@ import (
|
|||||||
core "github.com/xtls/xray-core/core"
|
core "github.com/xtls/xray-core/core"
|
||||||
"github.com/xtls/xray-core/proxy/freedom"
|
"github.com/xtls/xray-core/proxy/freedom"
|
||||||
"github.com/xtls/xray-core/proxy/masque"
|
"github.com/xtls/xray-core/proxy/masque"
|
||||||
"github.com/xtls/xray-core/proxy/vless"
|
|
||||||
"github.com/xtls/xray-core/proxy/vless/inbound"
|
|
||||||
"github.com/xtls/xray-core/proxy/vless/outbound"
|
|
||||||
"github.com/xtls/xray-core/transport/internet"
|
"github.com/xtls/xray-core/transport/internet"
|
||||||
"google.golang.org/protobuf/proto"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -213,9 +209,6 @@ func (c *InboundDetourConfig) Build() (*core.InboundHandlerConfig, error) {
|
|||||||
if _, ok := ts.(*masque.ServerConfig); !ok && receiverSettings.StreamSettings != nil && receiverSettings.StreamSettings.ProtocolName == "masque" {
|
if _, ok := ts.(*masque.ServerConfig); !ok && receiverSettings.StreamSettings != nil && receiverSettings.StreamSettings.ProtocolName == "masque" {
|
||||||
return nil, errors.New("the masque transport can only be used by the masque inbound")
|
return nil, errors.New("the masque transport can only be used by the masque inbound")
|
||||||
}
|
}
|
||||||
if err := validateXDrive(ts, receiverSettings.StreamSettings, nil, receiverSettings.PortList); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &core.InboundHandlerConfig{
|
return &core.InboundHandlerConfig{
|
||||||
Tag: c.Tag,
|
Tag: c.Tag,
|
||||||
@@ -269,54 +262,6 @@ func validateOutboundTransportSecurity(rawConfig interface{}, senderSettings *pr
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateXDrive allows only VLESS with encryption on xdrive, and on the client Mux.Cool for every connection
|
|
||||||
func validateXDrive(ts proto.Message, ss *internet.StreamConfig, ms *proxyman.MultiplexingConfig, pl *net.PortList) error {
|
|
||||||
if ss == nil || ss.ProtocolName != "xdrive" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
switch c := ts.(type) {
|
|
||||||
case *outbound.Config:
|
|
||||||
a, err := c.Vnext.User.Account.GetInstance()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
account := a.(*vless.Account)
|
|
||||||
if account.Flow != "" {
|
|
||||||
return errors.New(`the xdrive transport doesn't support VLESS "flow"`)
|
|
||||||
}
|
|
||||||
if account.Encryption == "none" {
|
|
||||||
return errors.New(`the xdrive transport needs VLESS "encryption", run "xray vlessenc" to get one`)
|
|
||||||
}
|
|
||||||
// reverse opens its own mux
|
|
||||||
if account.Reverse == nil && (ms == nil || !ms.Enabled || ms.Concurrency < 0 || ms.XudpConcurrency < 0 || ms.XudpProxyUDP443 == "skip") {
|
|
||||||
return errors.New(`the xdrive transport needs "mux" enabled, with no negative "concurrency" or "xudpConcurrency" and no "xudpProxyUDP443":"skip"`)
|
|
||||||
}
|
|
||||||
if account.Testpre != 0 {
|
|
||||||
return errors.New(`the xdrive transport doesn't support VLESS "testpre"`)
|
|
||||||
}
|
|
||||||
case *inbound.Config:
|
|
||||||
if c.Decryption == "none" {
|
|
||||||
return errors.New(`the xdrive transport needs VLESS "decryption", run "xray vlessenc" to get one`)
|
|
||||||
}
|
|
||||||
for _, user := range c.Users {
|
|
||||||
a, err := user.Account.GetInstance()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if a.(*vless.Account).Flow != "" {
|
|
||||||
return errors.New(`the xdrive transport doesn't support VLESS "flow"`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// every port gets its own listener on the same folder, and each of them accepts the same session
|
|
||||||
if pl != nil && len(pl.Ports()) != 1 {
|
|
||||||
return errors.New("the xdrive transport can only listen on one port")
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return errors.New("the xdrive transport can only be used by VLESS")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build implements Buildable.
|
// Build implements Buildable.
|
||||||
func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) {
|
func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) {
|
||||||
if c.ProxySettings != nil {
|
if c.ProxySettings != nil {
|
||||||
@@ -395,9 +340,6 @@ func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("failed to build outbound handler for protocol ", c.Protocol).Base(err)
|
return nil, errors.New("failed to build outbound handler for protocol ", c.Protocol).Base(err)
|
||||||
}
|
}
|
||||||
if err := validateXDrive(ts, senderSettings.StreamSettings, senderSettings.MultiplexSettings, nil); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := validateOutboundTransportSecurity(rawConfig, senderSettings); err != nil {
|
if err := validateOutboundTransportSecurity(rawConfig, senderSettings); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
package all
|
package all
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/mldsa"
|
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/cloudflare/circl/sign/mldsa/mldsa65"
|
||||||
"github.com/xtls/xray-core/main/commands/base"
|
"github.com/xtls/xray-core/main/commands/base"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -39,8 +39,8 @@ func executeMLDSA65(cmd *base.Command, args []string) {
|
|||||||
} else {
|
} else {
|
||||||
rand.Read(seed[:])
|
rand.Read(seed[:])
|
||||||
}
|
}
|
||||||
key, _ := mldsa.NewPrivateKey(mldsa.MLDSA65(), seed[:])
|
pub, _ := mldsa65.NewKeyFromSeed(&seed)
|
||||||
fmt.Printf("Seed: %v\nVerify: %v\n",
|
fmt.Printf("Seed: %v\nVerify: %v\n",
|
||||||
base64.RawURLEncoding.EncodeToString(seed[:]),
|
base64.RawURLEncoding.EncodeToString(seed[:]),
|
||||||
base64.RawURLEncoding.EncodeToString(key.PublicKey().Bytes()))
|
base64.RawURLEncoding.EncodeToString(pub.Bytes()))
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-30
@@ -10,7 +10,6 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"sync"
|
"sync"
|
||||||
"text/template"
|
"text/template"
|
||||||
"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/buf"
|
||||||
@@ -87,13 +86,7 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter
|
|||||||
user := server.User
|
user := server.User
|
||||||
var conn stat.Connection
|
var conn stat.Connection
|
||||||
|
|
||||||
var mbuf buf.MultiBuffer
|
mbuf, _ := link.Reader.ReadMultiBuffer()
|
||||||
if reader, ok := link.Reader.(buf.TimeoutReader); ok {
|
|
||||||
// Don't wait forever: server-first protocols (SMTP, FTP, MySQL...) send nothing until the server greets.
|
|
||||||
mbuf, _ = reader.ReadMultiBufferTimeout(time.Millisecond * 500)
|
|
||||||
} else {
|
|
||||||
mbuf, _ = link.Reader.ReadMultiBuffer()
|
|
||||||
}
|
|
||||||
len := mbuf.Len()
|
len := mbuf.Len()
|
||||||
firstPayload := bytespool.Alloc(len)
|
firstPayload := bytespool.Alloc(len)
|
||||||
mbuf, _ = buf.SplitBytes(mbuf, firstPayload)
|
mbuf, _ = buf.SplitBytes(mbuf, firstPayload)
|
||||||
@@ -241,8 +234,7 @@ func setUpHTTPTunnel(ctx context.Context, dest net.Destination, target string, u
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
reader := bufio.NewReader(rawConn)
|
resp, err := http.ReadResponse(bufio.NewReader(rawConn), req)
|
||||||
resp, err := http.ReadResponse(reader, req)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rawConn.Close()
|
rawConn.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -253,11 +245,6 @@ func setUpHTTPTunnel(ctx context.Context, dest net.Destination, target string, u
|
|||||||
rawConn.Close()
|
rawConn.Close()
|
||||||
return nil, errors.New("Proxy responded with non 200 code: " + resp.Status)
|
return nil, errors.New("Proxy responded with non 200 code: " + resp.Status)
|
||||||
}
|
}
|
||||||
// Tunneled bytes (e.g. a server-first greeting) may arrive in the same read as the 200 response.
|
|
||||||
if n := reader.Buffered(); n > 0 {
|
|
||||||
early, _ := reader.Peek(n)
|
|
||||||
return &earlyDataConn{Conn: rawConn, early: early}, nil
|
|
||||||
}
|
|
||||||
return rawConn, nil
|
return rawConn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -388,21 +375,6 @@ func (h *http2Conn) Close() error {
|
|||||||
return h.out.Close()
|
return h.out.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// earlyDataConn returns the bytes read ahead together with the CONNECT response before reading from Conn.
|
|
||||||
type earlyDataConn struct {
|
|
||||||
net.Conn
|
|
||||||
early []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *earlyDataConn) Read(p []byte) (int, error) {
|
|
||||||
if len(c.early) > 0 {
|
|
||||||
n := copy(p, c.early)
|
|
||||||
c.early = c.early[n:]
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
return c.Conn.Read(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
common.Must(common.RegisterConfig((*ClientConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
common.Must(common.RegisterConfig((*ClientConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
||||||
return NewClient(ctx, config.(*ClientConfig))
|
return NewClient(ctx, config.(*ClientConfig))
|
||||||
|
|||||||
@@ -327,7 +327,7 @@ func newTunnel(conn stat.Connection, local []netip.Addr, remoteDNS []netip.Addr,
|
|||||||
dns = remoteDNS
|
dns = remoteDNS
|
||||||
}
|
}
|
||||||
|
|
||||||
dev, tnet, err := wireguard.CreateNetTUN(local, dns, masque.MinPacketSize, nil)
|
dev, tnet, _, err := wireguard.CreateNetTUN(local, dns, masque.MinPacketSize, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-13
@@ -22,7 +22,6 @@ import (
|
|||||||
"github.com/xtls/xray-core/common/session"
|
"github.com/xtls/xray-core/common/session"
|
||||||
"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"
|
||||||
xtun "github.com/xtls/xray-core/proxy/tun"
|
|
||||||
"github.com/xtls/xray-core/proxy/wireguard"
|
"github.com/xtls/xray-core/proxy/wireguard"
|
||||||
"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"
|
||||||
@@ -167,11 +166,17 @@ func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {
|
|||||||
if mtu == 0 {
|
if mtu == 0 {
|
||||||
mtu = masque.MinPacketSize
|
mtu = masque.MinPacketSize
|
||||||
}
|
}
|
||||||
|
dev, _, gstack, err := wireguard.CreateNetTUN(local, nil, mtu, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
s := &Server{
|
s := &Server{
|
||||||
validator: users,
|
validator: users,
|
||||||
dispatcher: v.GetFeature(routing.DispatcherType()).(routing.Dispatcher),
|
dispatcher: v.GetFeature(routing.DispatcherType()).(routing.Dispatcher),
|
||||||
ctx: core.ToBackgroundDetachedContext(ctx),
|
ctx: core.ToBackgroundDetachedContext(ctx),
|
||||||
mtu: mtu,
|
mtu: mtu,
|
||||||
|
dev: dev,
|
||||||
pools: pools,
|
pools: pools,
|
||||||
local: local,
|
local: local,
|
||||||
tunnels: make(map[netip.Addr]*serverTunnel),
|
tunnels: make(map[netip.Addr]*serverTunnel),
|
||||||
@@ -182,11 +187,7 @@ func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {
|
|||||||
if content := session.ContentFromContext(ctx); content != nil {
|
if content := session.ContentFromContext(ctx); content != nil {
|
||||||
s.sniffing = content.SniffingRequest
|
s.sniffing = content.SniffingRequest
|
||||||
}
|
}
|
||||||
dev, _, err := wireguard.CreateNetTUN(local, nil, mtu, s)
|
wireguard.CreateForwarder(gstack, s.handleConnection)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
s.dev = dev
|
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -499,8 +500,7 @@ func packetDestination(packet []byte) (netip.Addr, bool) {
|
|||||||
return netip.Addr{}, false
|
return netip.Addr{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleConnection implements tun.ConnectionHandler
|
func (s *Server) handleConnection(conn net.Conn, dest net.Destination) {
|
||||||
func (s *Server) HandleConnection(conn net.Conn, dest net.Destination) {
|
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
source := net.DestinationFromAddr(conn.RemoteAddr())
|
source := net.DestinationFromAddr(conn.RemoteAddr())
|
||||||
addr, _ := netip.AddrFromSlice(source.Address.IP())
|
addr, _ := netip.AddrFromSlice(source.Address.IP())
|
||||||
@@ -534,13 +534,9 @@ func (s *Server) HandleConnection(conn net.Conn, dest net.Destination) {
|
|||||||
})
|
})
|
||||||
errors.LogInfo(ctx, "processing from ", source, " to ", dest)
|
errors.LogInfo(ctx, "processing from ", source, " to ", dest)
|
||||||
|
|
||||||
writer := buf.NewWriter(conn)
|
|
||||||
if dest.Network == net.Network_TCP {
|
|
||||||
writer = &xtun.TCPWriter{Writer: writer}
|
|
||||||
}
|
|
||||||
link := &transport.Link{
|
link := &transport.Link{
|
||||||
Reader: &buf.TimeoutWrapperReader{Reader: buf.NewReader(conn)},
|
Reader: &buf.TimeoutWrapperReader{Reader: buf.NewReader(conn)},
|
||||||
Writer: writer,
|
Writer: buf.NewWriter(conn),
|
||||||
}
|
}
|
||||||
if err := s.dispatcher.DispatchLink(ctx, dest, link); err != nil {
|
if err := s.dispatcher.DispatchLink(ctx, dest, link); err != nil {
|
||||||
errors.LogError(ctx, errors.New("connection closed").Base(err))
|
errors.LogError(ctx, errors.New("connection closed").Base(err))
|
||||||
|
|||||||
@@ -7,17 +7,13 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"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/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/features/routing"
|
|
||||||
"github.com/xtls/xray-core/transport"
|
|
||||||
"golang.zx2c4.com/wireguard/tun"
|
"golang.zx2c4.com/wireguard/tun"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -330,56 +326,3 @@ func TestPacketDestination(t *testing.T) {
|
|||||||
require.False(t, ok)
|
require.False(t, ok)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// stackConn is a TCP connection as the stack hands it over: it is its own buf.Writer.
|
|
||||||
type stackConn struct {
|
|
||||||
net.Conn
|
|
||||||
closed atomic.Int32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *stackConn) Read([]byte) (int, error) { return 0, io.EOF }
|
|
||||||
|
|
||||||
func (c *stackConn) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
|
||||||
buf.ReleaseMulti(mb)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *stackConn) Close() error {
|
|
||||||
c.closed.Add(1)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *stackConn) RemoteAddr() net.Addr {
|
|
||||||
return &net.TCPAddr{IP: net.IP{10, 13, 0, 2}, Port: 1234}
|
|
||||||
}
|
|
||||||
|
|
||||||
// closingDispatcher does what an outbound does when the other side has no more to send.
|
|
||||||
type closingDispatcher struct {
|
|
||||||
routing.Dispatcher
|
|
||||||
conn *stackConn
|
|
||||||
closed int32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *closingDispatcher) DispatchLink(_ context.Context, _ net.Destination, link *transport.Link) error {
|
|
||||||
common.Close(link.Writer)
|
|
||||||
d.closed = d.conn.closed.Load()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestServerKeepsConnectionOpenWhenWriterCloses(t *testing.T) {
|
|
||||||
conn := &stackConn{}
|
|
||||||
dispatcher := &closingDispatcher{conn: conn}
|
|
||||||
s := &Server{
|
|
||||||
ctx: context.Background(),
|
|
||||||
dispatcher: dispatcher,
|
|
||||||
tunnels: map[netip.Addr]*serverTunnel{netip.MustParseAddr("10.13.0.2"): newServerTunnel(nil, &protocol.MemoryUser{})},
|
|
||||||
}
|
|
||||||
s.HandleConnection(conn, net.TCPDestination(net.ParseAddress("192.0.2.1"), 443))
|
|
||||||
|
|
||||||
if dispatcher.closed != 0 {
|
|
||||||
t.Fatal("the connection was closed with its writer, while the client may still be sending")
|
|
||||||
}
|
|
||||||
if got := conn.closed.Load(); got != 1 {
|
|
||||||
t.Fatalf("connection closed %d times, want 1", got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+6
-9
@@ -180,14 +180,14 @@ type VisionReader struct {
|
|||||||
isUplink bool
|
isUplink bool
|
||||||
conn net.Conn
|
conn net.Conn
|
||||||
input *bytes.Reader
|
input *bytes.Reader
|
||||||
rawInput **bytes.Buffer
|
rawInput *bytes.Buffer
|
||||||
ob *session.Outbound
|
ob *session.Outbound
|
||||||
|
|
||||||
// internal
|
// internal
|
||||||
directReadCounter stats.Counter
|
directReadCounter stats.Counter
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewVisionReader(reader buf.Reader, trafficState *TrafficState, isUplink bool, ctx context.Context, conn net.Conn, input *bytes.Reader, rawInput **bytes.Buffer, ob *session.Outbound) *VisionReader {
|
func NewVisionReader(reader buf.Reader, trafficState *TrafficState, isUplink bool, ctx context.Context, conn net.Conn, input *bytes.Reader, rawInput *bytes.Buffer, ob *session.Outbound) *VisionReader {
|
||||||
return &VisionReader{
|
return &VisionReader{
|
||||||
Reader: reader,
|
Reader: reader,
|
||||||
trafficState: trafficState,
|
trafficState: trafficState,
|
||||||
@@ -261,15 +261,12 @@ func (w *VisionReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
|||||||
if inputBuffer, err := buf.ReadFrom(w.input); err == nil && !inputBuffer.IsEmpty() {
|
if inputBuffer, err := buf.ReadFrom(w.input); err == nil && !inputBuffer.IsEmpty() {
|
||||||
buffer, _ = buf.MergeMulti(buffer, inputBuffer)
|
buffer, _ = buf.MergeMulti(buffer, inputBuffer)
|
||||||
}
|
}
|
||||||
*w.input = bytes.Reader{} // release memory
|
if rawInputBuffer, err := buf.ReadFrom(w.rawInput); err == nil && !rawInputBuffer.IsEmpty() {
|
||||||
w.input = nil
|
|
||||||
|
|
||||||
if raw := *w.rawInput; raw != nil {
|
|
||||||
if rawInputBuffer, err := buf.ReadFrom(raw); err == nil && !rawInputBuffer.IsEmpty() {
|
|
||||||
buffer, _ = buf.MergeMulti(buffer, rawInputBuffer)
|
buffer, _ = buf.MergeMulti(buffer, rawInputBuffer)
|
||||||
}
|
}
|
||||||
// *raw = bytes.Buffer{} // may not be suitable for Go 1.27.3+
|
*w.input = bytes.Reader{} // release memory
|
||||||
}
|
w.input = nil
|
||||||
|
*w.rawInput = bytes.Buffer{} // release memory
|
||||||
w.rawInput = nil
|
w.rawInput = nil
|
||||||
|
|
||||||
if inbound := session.InboundFromContext(w.ctx); inbound != nil && inbound.Conn != nil {
|
if inbound := session.InboundFromContext(w.ctx); inbound != nil && inbound.Conn != nil {
|
||||||
|
|||||||
@@ -169,18 +169,12 @@ func (i *Inbound) processUDP(ctx context.Context, conn stat.Connection, dispatch
|
|||||||
}
|
}
|
||||||
sessionItem.Unlock()
|
sessionItem.Unlock()
|
||||||
}
|
}
|
||||||
link := sessionItem.link.Load()
|
link, err := sessionItem.EnsureLink(ctx, conn, decoded.Destination, dispatcher, i.policyManager, func(dest net.Destination, payload []byte) ([]byte, error) {
|
||||||
if link != nil {
|
|
||||||
sessionItem.UpdateConn(conn)
|
|
||||||
} else {
|
|
||||||
var err error
|
|
||||||
link, err = sessionItem.EnsureLink(ctx, conn, decoded.Destination, dispatcher, i.policyManager, func(dest net.Destination, payload []byte) ([]byte, error) {
|
|
||||||
return i.udpCodec.EncodeServerPacket(decoded.SessionID, dest, payload)
|
return i.udpCodec.EncodeServerPacket(decoded.SessionID, dest, payload)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
payloadBuf := buf.New()
|
payloadBuf := buf.New()
|
||||||
payloadBuf.Write(decoded.Payload)
|
payloadBuf.Write(decoded.Payload)
|
||||||
|
|||||||
@@ -336,18 +336,12 @@ func (i *MultiUserInbound) processUDP(ctx context.Context, conn stat.Connection,
|
|||||||
}
|
}
|
||||||
sessionItem.Unlock()
|
sessionItem.Unlock()
|
||||||
|
|
||||||
link := sessionItem.link.Load()
|
link, err := sessionItem.EnsureLink(ctx, conn, decoded.Destination, dispatcher, i.policyManager, func(replyDest net.Destination, payload []byte) ([]byte, error) {
|
||||||
if link != nil {
|
|
||||||
sessionItem.UpdateConn(conn)
|
|
||||||
} else {
|
|
||||||
var err error
|
|
||||||
link, err = sessionItem.EnsureLink(ctx, conn, decoded.Destination, dispatcher, i.policyManager, func(replyDest net.Destination, payload []byte) ([]byte, error) {
|
|
||||||
return i.encodeServerUDPPacket(sessionID, userPSK, replyDest, payload)
|
return i.encodeServerUDPPacket(sessionID, userPSK, replyDest, payload)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pBuf := buf.New()
|
pBuf := buf.New()
|
||||||
pBuf.Write(decoded.Payload)
|
pBuf.Write(decoded.Payload)
|
||||||
|
|||||||
@@ -253,17 +253,11 @@ func (i *RelayInbound) processUDP(ctx context.Context, conn stat.Connection, dis
|
|||||||
}
|
}
|
||||||
sessionItem.Unlock()
|
sessionItem.Unlock()
|
||||||
}
|
}
|
||||||
link := sessionItem.link.Load()
|
link, err := sessionItem.EnsureLink(ctx, conn, dest, dispatcher, i.policyManager, nil)
|
||||||
if link != nil {
|
|
||||||
sessionItem.UpdateConn(conn)
|
|
||||||
} else {
|
|
||||||
var err error
|
|
||||||
link, err = sessionItem.EnsureLink(ctx, conn, dest, dispatcher, i.policyManager, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Release()
|
b.Release()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
_ = link.Writer.WriteMultiBuffer(buf.MultiBuffer{b})
|
_ = link.Writer.WriteMultiBuffer(buf.MultiBuffer{b})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -169,8 +169,8 @@ func (o *Outbound) Process(ctx context.Context, link *transport.Link, dialer int
|
|||||||
return buf.Copy(responseReader, link.Writer, buf.UpdateActivity(timer))
|
return buf.Copy(responseReader, link.Writer, buf.UpdateActivity(timer))
|
||||||
}
|
}
|
||||||
|
|
||||||
responseDonePost := task.OnSuccess(responseDone, task.Close(link.Writer))
|
responseDoneAndCloseWriter := task.OnSuccess(responseDone, task.Close(link.Writer))
|
||||||
if err := task.Run(ctx, requestDone, responseDonePost); err != nil {
|
if err := task.Run(ctx, requestDone, responseDoneAndCloseWriter); err != nil {
|
||||||
return errors.New("connection ends").Base(err)
|
return errors.New("connection ends").Base(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,8 +212,8 @@ func (o *Outbound) Process(ctx context.Context, link *transport.Link, dialer int
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
responseDonePost := task.OnSuccess(responseDone, task.Close(link.Writer))
|
responseDoneAndCloseWriter := task.OnSuccess(responseDone, task.Close(link.Writer))
|
||||||
if err := task.Run(ctx, requestDone, responseDonePost); err != nil {
|
if err := task.Run(ctx, requestDone, responseDoneAndCloseWriter); err != nil {
|
||||||
return errors.New("connection ends").Base(err)
|
return errors.New("connection ends").Base(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ func parsePlainUDPPacket(sessionID, packetID uint64, bodyPlain []byte) (DecodedU
|
|||||||
return DecodedUDPPacket{}, ErrBadHeaderType
|
return DecodedUDPPacket{}, ErrBadHeaderType
|
||||||
}
|
}
|
||||||
epoch := binary.BigEndian.Uint64(bodyPlain[1:9])
|
epoch := binary.BigEndian.Uint64(bodyPlain[1:9])
|
||||||
diff := math.Abs(float64(time.Now().Unix() - int64(epoch)))
|
diff := int(math.Abs(float64(time.Now().Unix() - int64(epoch))))
|
||||||
if diff > 30 {
|
if diff > 30 {
|
||||||
return DecodedUDPPacket{}, ErrBadTimestamp
|
return DecodedUDPPacket{}, ErrBadTimestamp
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,12 +49,12 @@ func (f *SlidingWindow) Add(counter uint64) {
|
|||||||
|
|
||||||
if counter > f.last {
|
if counter > f.last {
|
||||||
lastBlockIndex := f.last >> swBlockBitLog
|
lastBlockIndex := f.last >> swBlockBitLog
|
||||||
diff := blockIndex - lastBlockIndex
|
diff := int(blockIndex - lastBlockIndex)
|
||||||
if diff > swRingBlocks {
|
if diff > swRingBlocks {
|
||||||
diff = swRingBlocks
|
diff = swRingBlocks
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < int(diff); i++ {
|
for i := 0; i < diff; i++ {
|
||||||
lastBlockIndex = (lastBlockIndex + 1) & swBlockMask
|
lastBlockIndex = (lastBlockIndex + 1) & swBlockMask
|
||||||
f.ring[lastBlockIndex] = 0
|
f.ring[lastBlockIndex] = 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,9 +18,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (s *ServerUDPSession) UpdateConn(conn stat.Connection) {
|
func (s *ServerUDPSession) UpdateConn(conn stat.Connection) {
|
||||||
|
if s.currentConn.Load() == nil {
|
||||||
s.currentConn.Store(conn)
|
s.currentConn.Store(conn)
|
||||||
|
}
|
||||||
|
if s.timer != nil {
|
||||||
s.timer.Update()
|
s.timer.Update()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *ServerUDPSession) WriteToClient(b []byte) error {
|
func (s *ServerUDPSession) WriteToClient(b []byte) error {
|
||||||
connVal := s.currentConn.Load()
|
connVal := s.currentConn.Load()
|
||||||
@@ -53,16 +57,19 @@ func (s *ServerUDPSession) EnsureLink(
|
|||||||
policyManager policy.Manager,
|
policyManager policy.Manager,
|
||||||
responseEncoder func(dest net.Destination, payload []byte) ([]byte, error),
|
responseEncoder func(dest net.Destination, payload []byte) ([]byte, error),
|
||||||
) (*transport.Link, error) {
|
) (*transport.Link, error) {
|
||||||
|
s.UpdateConn(conn)
|
||||||
|
|
||||||
|
if link := s.link.Load(); link != nil {
|
||||||
|
return link, nil
|
||||||
|
}
|
||||||
|
|
||||||
s.Lock()
|
s.Lock()
|
||||||
defer s.Unlock()
|
defer s.Unlock()
|
||||||
|
|
||||||
if link := s.link.Load(); link != nil {
|
if link := s.link.Load(); link != nil {
|
||||||
s.UpdateConn(conn)
|
|
||||||
return link, nil
|
return link, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
s.currentConn.Store(conn)
|
|
||||||
|
|
||||||
sessCtx, cancel := context.WithCancel(ctx)
|
sessCtx, cancel := context.WithCancel(ctx)
|
||||||
inbound := session.InboundFromContext(sessCtx)
|
inbound := session.InboundFromContext(sessCtx)
|
||||||
if inbound != nil && s.User != nil {
|
if inbound != nil && s.User != nil {
|
||||||
@@ -87,6 +94,7 @@ func (s *ServerUDPSession) EnsureLink(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.link.Store(link)
|
||||||
sessionPolicy := policyManager.ForLevel(level)
|
sessionPolicy := policyManager.ForLevel(level)
|
||||||
s.timer = signal.CancelAfterInactivity(sessCtx, func() {
|
s.timer = signal.CancelAfterInactivity(sessCtx, func() {
|
||||||
if s.manager != nil {
|
if s.manager != nil {
|
||||||
@@ -95,7 +103,6 @@ func (s *ServerUDPSession) EnsureLink(
|
|||||||
s.Close()
|
s.Close()
|
||||||
cancel()
|
cancel()
|
||||||
}, sessionPolicy.Timeouts.ConnectionIdle)
|
}, sessionPolicy.Timeouts.ConnectionIdle)
|
||||||
s.link.Store(link)
|
|
||||||
|
|
||||||
go handleUDPResponse(s, link, dest, responseEncoder)
|
go handleUDPResponse(s, link, dest, responseEncoder)
|
||||||
return link, nil
|
return link, nil
|
||||||
@@ -111,13 +118,19 @@ func ResetTCPConn(conn net.Conn) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func handleUDPResponse(s *ServerUDPSession, link *transport.Link, fallbackDest net.Destination, encode func(dest net.Destination, payload []byte) ([]byte, error)) {
|
func handleUDPResponse(s *ServerUDPSession, link *transport.Link, fallbackDest net.Destination, encode func(dest net.Destination, payload []byte) ([]byte, error)) {
|
||||||
defer s.timer.SetTimeout(0)
|
defer func() {
|
||||||
|
if s.timer != nil {
|
||||||
|
s.timer.SetTimeout(0)
|
||||||
|
}
|
||||||
|
}()
|
||||||
for {
|
for {
|
||||||
resMb, err := link.Reader.ReadMultiBuffer()
|
resMb, err := link.Reader.ReadMultiBuffer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if s.timer != nil {
|
||||||
s.timer.Update()
|
s.timer.Update()
|
||||||
|
}
|
||||||
for i, rb := range resMb {
|
for i, rb := range resMb {
|
||||||
b := rb.Bytes()
|
b := rb.Bytes()
|
||||||
if encode != nil {
|
if encode != nil {
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
|
||||||
"github.com/xtls/xray-core/common/antireplay"
|
"github.com/xtls/xray-core/common/antireplay"
|
||||||
"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"
|
||||||
@@ -254,7 +253,7 @@ func ReadClientRequestHeaderWithFixed(reader *StreamReader, fixedChunk []byte) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
epoch := binary.BigEndian.Uint64(plainFixed[1:9])
|
epoch := binary.BigEndian.Uint64(plainFixed[1:9])
|
||||||
diff := math.Abs(float64(time.Now().Unix() - int64(epoch)))
|
diff := int(math.Abs(float64(time.Now().Unix() - int64(epoch))))
|
||||||
if diff > 30 {
|
if diff > 30 {
|
||||||
return nil, ErrBadTimestamp
|
return nil, ErrBadTimestamp
|
||||||
}
|
}
|
||||||
@@ -431,7 +430,7 @@ func ReadTCPResponse(r io.Reader, method *CipherMethod, psk []byte, clientSalt [
|
|||||||
}
|
}
|
||||||
|
|
||||||
serverEpoch := binary.BigEndian.Uint64(decryptedFixed[1:9])
|
serverEpoch := binary.BigEndian.Uint64(decryptedFixed[1:9])
|
||||||
diff := math.Abs(float64(time.Now().Unix() - int64(serverEpoch)))
|
diff := int(math.Abs(float64(time.Now().Unix() - int64(serverEpoch))))
|
||||||
if diff > 30 {
|
if diff > 30 {
|
||||||
return nil, ErrBadTimestamp
|
return nil, ErrBadTimestamp
|
||||||
}
|
}
|
||||||
@@ -630,7 +629,6 @@ func InitServerStream(conn net.Conn, method *CipherMethod, psk, saltSlice []byte
|
|||||||
func TransportTCP(ctx context.Context, sessionPolicy policy.Session, reader buf.Reader, writer buf.Writer, link *transport.Link) error {
|
func TransportTCP(ctx context.Context, sessionPolicy policy.Session, reader buf.Reader, writer buf.Writer, link *transport.Link) error {
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle)
|
timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle)
|
||||||
defer timer.SetTimeout(0)
|
|
||||||
ctx = policy.ContextWithBufferPolicy(ctx, sessionPolicy.Buffer)
|
ctx = policy.ContextWithBufferPolicy(ctx, sessionPolicy.Buffer)
|
||||||
|
|
||||||
requestDone := func() error {
|
requestDone := func() error {
|
||||||
@@ -646,12 +644,6 @@ func TransportTCP(ctx context.Context, sessionPolicy policy.Session, reader buf.
|
|||||||
return buf.Copy(link.Reader, writer, buf.UpdateActivity(timer))
|
return buf.Copy(link.Reader, writer, buf.UpdateActivity(timer))
|
||||||
}
|
}
|
||||||
|
|
||||||
requestDoneAndCloseWriter := task.OnSuccess(requestDone, task.Close(link.Writer))
|
responseDoneAndCloseWriter := task.OnSuccess(responseDone, task.Close(link.Writer))
|
||||||
if err := task.Run(ctx, requestDoneAndCloseWriter, responseDone); err != nil {
|
return task.Run(ctx, requestDone, responseDoneAndCloseWriter)
|
||||||
common.Interrupt(link.Reader)
|
|
||||||
common.Interrupt(link.Writer)
|
|
||||||
return errors.New("connection ends").Base(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ package socks
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/net"
|
|
||||||
"github.com/xtls/xray-core/common/signal"
|
"github.com/xtls/xray-core/common/signal"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,9 +14,6 @@ func NewTempUDPConn(udpConn net.PacketConn, tcpConn net.Conn, expectedRemote *ne
|
|||||||
PacketConn: udpConn,
|
PacketConn: udpConn,
|
||||||
AssociatedTCPConn: tcpConn,
|
AssociatedTCPConn: tcpConn,
|
||||||
}
|
}
|
||||||
t.Timer = signal.CancelAfterInactivity(context.Background(), func() {
|
|
||||||
t.Close()
|
|
||||||
}, net.ConnIdleTimeout)
|
|
||||||
t.ExpectedRemote.Store(expectedRemote)
|
t.ExpectedRemote.Store(expectedRemote)
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
@@ -63,7 +60,9 @@ func (c *TempUDPConn) RemoteAddr() net.Addr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *TempUDPConn) SetTimeout(d time.Duration) {
|
func (c *TempUDPConn) SetTimeout(d time.Duration) {
|
||||||
c.Timer.SetTimeout(d)
|
c.Timer = signal.CancelAfterInactivity(context.Background(), func() {
|
||||||
|
c.Close()
|
||||||
|
}, d)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *TempUDPConn) Close() error {
|
func (c *TempUDPConn) Close() error {
|
||||||
|
|||||||
+12
-18
@@ -56,21 +56,20 @@ func (w *tunUDPStatsWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TCPWriter is the writing side of a TCP connection of the stack for the dispatcher. It has no Close: the outbound
|
// tunTCPWriter is the writing side of a TCP connection for the dispatcher. It has no Close: the outbound closes
|
||||||
// closes its writer when the other side has no more to send, and the system may still be sending
|
// its writer when the other side has no more to send, and the system may still be sending
|
||||||
type TCPWriter struct {
|
type tunTCPWriter struct {
|
||||||
Writer buf.Writer
|
writer buf.Writer
|
||||||
// Counter counts what is written, if there is one
|
counter stats.Counter
|
||||||
Counter stats.Counter
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TCPWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
func (w *tunTCPWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
||||||
size := mb.Len()
|
size := mb.Len()
|
||||||
if err := w.Writer.WriteMultiBuffer(mb); err != nil {
|
if err := w.writer.WriteMultiBuffer(mb); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if w.Counter != nil {
|
if w.counter != nil {
|
||||||
w.Counter.Add(int64(size))
|
w.counter.Add(int64(size))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -144,8 +143,7 @@ func (t *Handler) Start() error {
|
|||||||
errors.LogInfo(context.Background(), "[tun] falied to set interface > iface == nil")
|
errors.LogInfo(context.Background(), "[tun] falied to set interface > iface == nil")
|
||||||
return errors.New("iface not found")
|
return errors.New("iface not found")
|
||||||
}
|
}
|
||||||
var controlErr, sockErr error
|
return c.Control(func(fd uintptr) {
|
||||||
controlErr = c.Control(func(fd uintptr) {
|
|
||||||
addrPort, _ := netip.ParseAddrPort(address)
|
addrPort, _ := netip.ParseAddrPort(address)
|
||||||
// skip loopback
|
// skip loopback
|
||||||
if addrPort.Addr().IsLoopback() || strings.HasPrefix(strings.ToLower(address), "localhost:") {
|
if addrPort.Addr().IsLoopback() || strings.HasPrefix(strings.ToLower(address), "localhost:") {
|
||||||
@@ -153,13 +151,9 @@ func (t *Handler) Start() error {
|
|||||||
}
|
}
|
||||||
err := setinterface(network, address, fd, iface)
|
err := setinterface(network, address, fd, iface)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sockErr = errors.New("[tun] falied to set interface").Base(err)
|
errors.LogInfoInner(context.Background(), err, "[tun] falied to set interface")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if sockErr != nil {
|
|
||||||
return sockErr
|
|
||||||
}
|
|
||||||
return controlErr
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,7 +243,7 @@ func (t *Handler) HandleConnection(conn net.Conn, destination net.Destination) {
|
|||||||
reader := &buf.TimeoutWrapperReader{Reader: buf.NewReader(conn), Counter: t.uplinkCounter}
|
reader := &buf.TimeoutWrapperReader{Reader: buf.NewReader(conn), Counter: t.uplinkCounter}
|
||||||
writer := buf.NewWriter(conn)
|
writer := buf.NewWriter(conn)
|
||||||
if destination.Network != net.Network_UDP {
|
if destination.Network != net.Network_UDP {
|
||||||
writer = &TCPWriter{Writer: writer, Counter: t.downlinkCounter}
|
writer = &tunTCPWriter{writer: writer, counter: t.downlinkCounter}
|
||||||
} else if t.downlinkCounter != nil {
|
} else if t.downlinkCounter != nil {
|
||||||
writer = &tunUDPStatsWriter{writer: writer, counter: t.downlinkCounter}
|
writer = &tunUDPStatsWriter{writer: writer, counter: t.downlinkCounter}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
+7
-237
@@ -2,14 +2,8 @@ package tun
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"math"
|
|
||||||
"math/bits"
|
"math/bits"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"slices"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -22,38 +16,18 @@ const (
|
|||||||
protoTCP = 6
|
protoTCP = 6
|
||||||
protoUDP = 17
|
protoUDP = 17
|
||||||
protoICMPv6 = 58
|
protoICMPv6 = 58
|
||||||
|
|
||||||
// the smallest that a link is believed to take whatever is said about it: what every IPv4 host takes,
|
|
||||||
// and the smallest link that IPv6 runs on
|
|
||||||
ipv4SmallestMTU = 576
|
|
||||||
ipv6SmallestMTU = 1280
|
|
||||||
|
|
||||||
// the header that an IPv6 packet has in front of each of its fragments, and its number
|
|
||||||
fragmentHeaderSize = 8
|
|
||||||
protoFragment = 44
|
|
||||||
// fragmentsMost is how many packets can be waiting for the rest of their fragments,
|
|
||||||
// fragmentsTime how long they are waited for at most
|
|
||||||
fragmentsMost = 64
|
|
||||||
fragmentsTime = 30 * time.Second
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// packet is an IP packet with its header read
|
// packet is an IP packet with its header read
|
||||||
type packet struct {
|
type packet struct {
|
||||||
proto byte
|
proto byte
|
||||||
src, dst netip.Addr
|
src, dst netip.Addr
|
||||||
// payload is what follows the IP header, without the last rest bytes of it when they are in other buffers
|
// payload is what follows the IP header
|
||||||
payload []byte
|
payload []byte
|
||||||
rest int
|
|
||||||
// fragment is set if payload is a part of what follows the IP header: the one at offset of the packet
|
|
||||||
// with the number id, which has more after it or not
|
|
||||||
fragment bool
|
|
||||||
more bool
|
|
||||||
offset int
|
|
||||||
id uint32
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// parsePacket reads the IP header of a packet that is b and rest bytes after it
|
// parsePacket reads the IP header of b. Fragments are not accepted: what the system sends to the tun fits its MTU
|
||||||
func parsePacket(b []byte, rest int) (p packet, ok bool) {
|
func parsePacket(b []byte) (p packet, ok bool) {
|
||||||
if len(b) < ipv4HeaderSize {
|
if len(b) < ipv4HeaderSize {
|
||||||
return p, false
|
return p, false
|
||||||
}
|
}
|
||||||
@@ -61,28 +35,25 @@ func parsePacket(b []byte, rest int) (p packet, ok bool) {
|
|||||||
case 4:
|
case 4:
|
||||||
size := int(b[0]&0xf) * 4
|
size := int(b[0]&0xf) * 4
|
||||||
total := int(binary.BigEndian.Uint16(b[2:]))
|
total := int(binary.BigEndian.Uint16(b[2:]))
|
||||||
if size < ipv4HeaderSize || total < size || total > len(b)+rest {
|
if size < ipv4HeaderSize || total < size || total > len(b) || binary.BigEndian.Uint16(b[6:])&0x3fff != 0 {
|
||||||
return p, false
|
return p, false
|
||||||
}
|
}
|
||||||
if f := binary.BigEndian.Uint16(b[6:]); f&0x3fff != 0 {
|
|
||||||
p.fragment, p.more, p.offset, p.id = true, f&0x2000 != 0, int(f&0x1fff)*8, uint32(binary.BigEndian.Uint16(b[4:]))
|
|
||||||
}
|
|
||||||
p.proto = b[9]
|
p.proto = b[9]
|
||||||
p.src = netip.AddrFrom4([4]byte(b[12:16]))
|
p.src = netip.AddrFrom4([4]byte(b[12:16]))
|
||||||
p.dst = netip.AddrFrom4([4]byte(b[16:20]))
|
p.dst = netip.AddrFrom4([4]byte(b[16:20]))
|
||||||
p.payload, p.rest = b[size:min(total, len(b))], max(total-len(b), 0)
|
p.payload = b[size:total]
|
||||||
case 6:
|
case 6:
|
||||||
if len(b) < ipv6HeaderSize {
|
if len(b) < ipv6HeaderSize {
|
||||||
return p, false
|
return p, false
|
||||||
}
|
}
|
||||||
total := ipv6HeaderSize + int(binary.BigEndian.Uint16(b[4:]))
|
total := ipv6HeaderSize + int(binary.BigEndian.Uint16(b[4:]))
|
||||||
if total > len(b)+rest {
|
if total > len(b) {
|
||||||
return p, false
|
return p, false
|
||||||
}
|
}
|
||||||
p.proto = b[6]
|
p.proto = b[6]
|
||||||
p.src = netip.AddrFrom16([16]byte(b[8:24]))
|
p.src = netip.AddrFrom16([16]byte(b[8:24]))
|
||||||
p.dst = netip.AddrFrom16([16]byte(b[24:40]))
|
p.dst = netip.AddrFrom16([16]byte(b[24:40]))
|
||||||
p.payload, p.rest = b[ipv6HeaderSize:min(total, len(b))], max(total-len(b), 0)
|
p.payload = b[ipv6HeaderSize:total]
|
||||||
// hop-by-hop options, routing, destination options
|
// hop-by-hop options, routing, destination options
|
||||||
for p.proto == 0 || p.proto == 43 || p.proto == 60 {
|
for p.proto == 0 || p.proto == 43 || p.proto == 60 {
|
||||||
if len(p.payload) < 8 {
|
if len(p.payload) < 8 {
|
||||||
@@ -94,55 +65,12 @@ func parsePacket(b []byte, rest int) (p packet, ok bool) {
|
|||||||
}
|
}
|
||||||
p.proto, p.payload = p.payload[0], p.payload[size:]
|
p.proto, p.payload = p.payload[0], p.payload[size:]
|
||||||
}
|
}
|
||||||
if p.proto == protoFragment {
|
|
||||||
f := p.payload
|
|
||||||
if len(f) < fragmentHeaderSize {
|
|
||||||
return p, false
|
|
||||||
}
|
|
||||||
p.fragment, p.more, p.offset, p.id = true, f[3]&1 != 0, int(binary.BigEndian.Uint16(f[2:])&^7), binary.BigEndian.Uint32(f[4:])
|
|
||||||
p.proto, p.payload = f[0], f[fragmentHeaderSize:]
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
return p, false
|
return p, false
|
||||||
}
|
}
|
||||||
return p, true
|
return p, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// verified reports whether the checksum of what the packet carries is right
|
|
||||||
func (p packet) verified() bool {
|
|
||||||
data := p.payload
|
|
||||||
switch p.proto {
|
|
||||||
case protoICMPv4:
|
|
||||||
return checksum(sum(data, 0)) == 0
|
|
||||||
case protoUDP:
|
|
||||||
if len(data) < udpHeaderSize {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// the packet may be longer than what it carries
|
|
||||||
size := int(binary.BigEndian.Uint16(data[4:]))
|
|
||||||
if size < udpHeaderSize || size > len(data) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if p.src.Is4() && data[6]|data[7] == 0 {
|
|
||||||
// zero stands for no checksum
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
data = data[:size]
|
|
||||||
case protoTCP, protoICMPv6:
|
|
||||||
default:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
pseudo := uint64(p.proto) + uint64(len(data))
|
|
||||||
if p.src.Is4() {
|
|
||||||
src, dst := p.src.As4(), p.dst.As4()
|
|
||||||
pseudo = sum(dst[:], sum(src[:], pseudo))
|
|
||||||
} else {
|
|
||||||
src, dst := p.src.As16(), p.dst.As16()
|
|
||||||
pseudo = sum(dst[:], sum(src[:], pseudo))
|
|
||||||
}
|
|
||||||
return checksum(sum(data, pseudo)) == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// putIPHeader writes the header of a packet from src to dst into b, without its length, and returns the size of the header
|
// putIPHeader writes the header of a packet from src to dst into b, without its length, and returns the size of the header
|
||||||
func putIPHeader(b []byte, proto byte, src, dst netip.Addr) int {
|
func putIPHeader(b []byte, proto byte, src, dst netip.Addr) int {
|
||||||
if src.Is4() {
|
if src.Is4() {
|
||||||
@@ -231,161 +159,3 @@ func checksum(s uint64) uint16 {
|
|||||||
s = s>>16 + s&0xffff
|
s = s>>16 + s&0xffff
|
||||||
return ^uint16(s)
|
return ^uint16(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeFragments writes a packet that is larger than mtu to tun in fragments that are not. header is its IP header
|
|
||||||
// with what follows that up to payload, which is no more than the header of a UDP packet. id is the number
|
|
||||||
// of the packet if it is an IPv6 one
|
|
||||||
func writeFragments(tun Tun, mtu int, id uint32, header, payload []byte) error {
|
|
||||||
var b [ipv6HeaderSize + fragmentHeaderSize + udpHeaderSize]byte
|
|
||||||
size := ipv4HeaderSize
|
|
||||||
if header[0]>>4 == 6 {
|
|
||||||
size = ipv6HeaderSize
|
|
||||||
}
|
|
||||||
n := copy(b[:], header[:size])
|
|
||||||
if size == ipv6HeaderSize {
|
|
||||||
// the number goes into a header of its own, behind the IP header. That of IPv4 has it
|
|
||||||
b[6], b[n] = protoFragment, header[6]
|
|
||||||
binary.BigEndian.PutUint32(b[n+4:], id)
|
|
||||||
n += fragmentHeaderSize
|
|
||||||
}
|
|
||||||
// first goes in front of payload, in the first fragment
|
|
||||||
first := header[size:]
|
|
||||||
if n+len(first) > len(b) {
|
|
||||||
return errors.New("no room for a header of ", len(first), " bytes in a fragment")
|
|
||||||
}
|
|
||||||
// every fragment but the last is a number of 8 bytes
|
|
||||||
room, total := (mtu-n)&^7, len(first)+len(payload)
|
|
||||||
if room < 8 {
|
|
||||||
return errors.New("no room for a fragment in an MTU of ", mtu)
|
|
||||||
}
|
|
||||||
for offset := 0; offset < total; {
|
|
||||||
h, part := b[:n], min(room, total-offset)
|
|
||||||
last := offset+part == total
|
|
||||||
if size == ipv6HeaderSize {
|
|
||||||
f := uint16(offset)
|
|
||||||
if !last {
|
|
||||||
f |= 1
|
|
||||||
}
|
|
||||||
binary.BigEndian.PutUint16(b[size+2:], f)
|
|
||||||
} else {
|
|
||||||
f := uint16(offset / 8)
|
|
||||||
if !last {
|
|
||||||
f |= 0x2000
|
|
||||||
}
|
|
||||||
binary.BigEndian.PutUint16(b[6:], f)
|
|
||||||
}
|
|
||||||
setIPLength(b[:size], n+part)
|
|
||||||
data := payload[max(offset-len(first), 0):min(offset+part-len(first), len(payload))]
|
|
||||||
if offset == 0 {
|
|
||||||
h = b[:n+copy(b[n:], first)]
|
|
||||||
}
|
|
||||||
if err := tun.WritePacket(h, data); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
offset += part
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// fragmented is a packet of which not every fragment has come yet
|
|
||||||
type fragmented struct {
|
|
||||||
// data is what follows its IP header, as far as its last fragment that has come goes
|
|
||||||
data []byte
|
|
||||||
// parts are what of data has come, in order and apart from each other
|
|
||||||
parts [][2]int
|
|
||||||
// size is that of data when all of it has come, known from the last fragment
|
|
||||||
size int
|
|
||||||
known bool
|
|
||||||
since time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
// reassembly puts the packets that come in fragments together
|
|
||||||
type reassembly struct {
|
|
||||||
mu sync.Mutex
|
|
||||||
packets map[fragments]*fragmented
|
|
||||||
}
|
|
||||||
|
|
||||||
// fragments is what the fragments of a packet have in common
|
|
||||||
type fragments struct {
|
|
||||||
src, dst netip.Addr
|
|
||||||
proto byte
|
|
||||||
id uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// add takes the fragment p of a packet. With the last one that was missing, it returns the packet.
|
|
||||||
// A packet whose fragments do not fit each other is given up
|
|
||||||
func (r *reassembly) add(p packet) (packet, bool) {
|
|
||||||
fragment, offset, end := p.payload, p.offset, p.offset+len(p.payload)
|
|
||||||
// what an IP packet can carry
|
|
||||||
most := math.MaxUint16
|
|
||||||
if p.src.Is4() {
|
|
||||||
most -= ipv4HeaderSize
|
|
||||||
}
|
|
||||||
if len(fragment) == 0 || end > most || p.more && len(fragment)%8 != 0 {
|
|
||||||
return p, false
|
|
||||||
}
|
|
||||||
key, now := fragments{p.src, p.dst, p.proto, p.id}, time.Now()
|
|
||||||
r.mu.Lock()
|
|
||||||
defer r.mu.Unlock()
|
|
||||||
f := r.packets[key]
|
|
||||||
if f != nil && now.Sub(f.since) > fragmentsTime {
|
|
||||||
// what is left of a packet whose other fragments were lost: this is one of another packet with its number
|
|
||||||
f = nil
|
|
||||||
}
|
|
||||||
if f == nil {
|
|
||||||
if _, stale := r.packets[key]; !stale && len(r.packets) >= fragmentsMost {
|
|
||||||
// the one that is waited for the longest makes room, not the one that comes now
|
|
||||||
var longest fragments
|
|
||||||
var since time.Time
|
|
||||||
for other, old := range r.packets {
|
|
||||||
if since.IsZero() || old.since.Before(since) {
|
|
||||||
longest, since = other, old.since
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delete(r.packets, longest)
|
|
||||||
}
|
|
||||||
if r.packets == nil {
|
|
||||||
r.packets = make(map[fragments]*fragmented)
|
|
||||||
}
|
|
||||||
f = &fragmented{since: now}
|
|
||||||
r.packets[key] = f
|
|
||||||
}
|
|
||||||
for _, part := range f.parts {
|
|
||||||
if offset >= part[0] && end <= part[1] {
|
|
||||||
// came already
|
|
||||||
return p, false
|
|
||||||
}
|
|
||||||
if offset < part[1] && part[0] < end {
|
|
||||||
// fragments of one packet do not overlap: these are of two, or made to be taken for something else
|
|
||||||
delete(r.packets, key)
|
|
||||||
return p, false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if f.known && end > f.size || !p.more && len(f.data) > end {
|
|
||||||
// past its end, or an end before what came
|
|
||||||
delete(r.packets, key)
|
|
||||||
return p, false
|
|
||||||
}
|
|
||||||
if !p.more {
|
|
||||||
f.size, f.known = end, true
|
|
||||||
}
|
|
||||||
if len(f.data) < end {
|
|
||||||
f.data = append(f.data, make([]byte, end-len(f.data))...)
|
|
||||||
}
|
|
||||||
copy(f.data[offset:], fragment)
|
|
||||||
i := 0
|
|
||||||
for i < len(f.parts) && f.parts[i][1] < offset {
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
j := i
|
|
||||||
for ; j < len(f.parts) && f.parts[j][0] <= end; j++ {
|
|
||||||
offset, end = min(offset, f.parts[j][0]), max(end, f.parts[j][1])
|
|
||||||
}
|
|
||||||
f.parts = slices.Replace(f.parts, i, j, [2]int{offset, end})
|
|
||||||
if !f.known || f.parts[0] != [2]int{0, f.size} {
|
|
||||||
return p, false
|
|
||||||
}
|
|
||||||
delete(r.packets, key)
|
|
||||||
p.payload, p.fragment = f.data[:f.size], false
|
|
||||||
return p, true
|
|
||||||
}
|
|
||||||
|
|||||||
+30
-374
@@ -3,12 +3,7 @@ package tun
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"io"
|
|
||||||
"math"
|
|
||||||
"math/rand/v2"
|
|
||||||
gonet "net"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"slices"
|
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
@@ -20,16 +15,8 @@ import (
|
|||||||
|
|
||||||
// Stack interface implement ip protocol stack, bridging raw network packets and data streams
|
// Stack interface implement ip protocol stack, bridging raw network packets and data streams
|
||||||
type Stack interface {
|
type Stack interface {
|
||||||
// Start makes the stack read the packets of its tun
|
|
||||||
Start() error
|
Start() error
|
||||||
Close() error
|
Close() error
|
||||||
// Input takes a packet of a tun that hands them over instead of being read. The packet is not kept,
|
|
||||||
// but it may be written to. The packets of one connection have to come one after another
|
|
||||||
Input(packet []byte)
|
|
||||||
// DialTCP opens a connection to address from the one of StackOptions.Addresses that is of its family
|
|
||||||
DialTCP(ctx context.Context, address netip.AddrPort) (net.Conn, error)
|
|
||||||
// DialUDP opens a port on StackOptions.Addresses. What anyone sends to the port is read from it
|
|
||||||
DialUDP() (net.PacketConn, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// StackOptions for the stack implementation
|
// StackOptions for the stack implementation
|
||||||
@@ -37,12 +24,6 @@ type StackOptions struct {
|
|||||||
Tun Tun
|
Tun Tun
|
||||||
MTU uint32
|
MTU uint32
|
||||||
IdleTimeout time.Duration
|
IdleTimeout time.Duration
|
||||||
// Addresses are what the stack opens connections from
|
|
||||||
Addresses []netip.Addr
|
|
||||||
// Remote is set if the tun is a tunnel to another system and not the tun of this one: what comes through it
|
|
||||||
// has come a long way, so its checksums are looked at, nothing but that way limits the windows, and the
|
|
||||||
// segments of TCP carry the times of both sides if the other side has them
|
|
||||||
Remote bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// flow is what the packets of one connection have in common, src is the side of the system
|
// flow is what the packets of one connection have in common, src is the side of the system
|
||||||
@@ -52,8 +33,7 @@ type flow struct {
|
|||||||
|
|
||||||
// stack turns the packets that the system sends to the tun into connections and back.
|
// stack turns the packets that the system sends to the tun into connections and back.
|
||||||
// It has only what the two need to talk through a tun, and a local link when the system passes on
|
// It has only what the two need to talk through a tun, and a local link when the system passes on
|
||||||
// what others send, see tcpConn. The tun may also be a tunnel to a system that is far away, and the stack
|
// what others send, see tcpConn
|
||||||
// the one that opens the connections
|
|
||||||
type stack struct {
|
type stack struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
tun Tun
|
tun Tun
|
||||||
@@ -62,62 +42,32 @@ type stack struct {
|
|||||||
segmenter segmenter
|
segmenter segmenter
|
||||||
// window is the window of every connection
|
// window is the window of every connection
|
||||||
window int32
|
window int32
|
||||||
// verify is set if the checksums of what comes are looked at
|
// handle is given every new connection
|
||||||
verify bool
|
|
||||||
// stamps is set if connections carry the times of RFC 7323 when the other side does
|
|
||||||
stamps bool
|
|
||||||
// handle is given every new connection that the other side opens. Without it those are refused
|
|
||||||
handle func(conn net.Conn, destination net.Destination)
|
handle func(conn net.Conn, destination net.Destination)
|
||||||
udp *udpConnectionHandler
|
udp *udpConnectionHandler
|
||||||
// addresses are what connections are opened from
|
|
||||||
addresses []netip.Addr
|
|
||||||
// bufs is what read reads a packet into if the tun is a segmenter, one after the other, the headers in the first.
|
|
||||||
// One that keep took is nil. joined is for a packet that is in more than one and is needed in one piece
|
|
||||||
bufs buf.MultiBuffer
|
|
||||||
joined []byte
|
|
||||||
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
conns map[flow]*tcpConn
|
conns map[flow]*tcpConn
|
||||||
// sockets are the ports that DialUDP has opened
|
|
||||||
sockets map[netip.AddrPort]*udpSocket
|
|
||||||
closed bool
|
closed bool
|
||||||
// opening is how many of conns wait for the answer to their SYN or SYN-ACK
|
// opening is how many of conns wait for the answer to their SYN-ACK
|
||||||
opening atomic.Int32
|
opening atomic.Int32
|
||||||
// fragments are the packets that are larger than a link on their way and come in parts.
|
|
||||||
// identification numbers the ones that leave like that: a counter for each of a few groups of destinations,
|
|
||||||
// each from a start of its own, so that no two of them that go one way meet, and no destination learns how much
|
|
||||||
// goes to the others
|
|
||||||
fragments reassembly
|
|
||||||
identification [64]atomic.Uint32
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewStack builds new ip stack. Without a handler it takes no connections, only opens them
|
// NewStack builds new ip stack
|
||||||
func NewStack(ctx context.Context, options StackOptions, handler ConnectionHandler) (Stack, error) {
|
func NewStack(ctx context.Context, options StackOptions, handler *Handler) (Stack, error) {
|
||||||
s := &stack{
|
s := &stack{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
tun: options.Tun,
|
tun: options.Tun,
|
||||||
mtu: int(options.MTU),
|
mtu: int(options.MTU),
|
||||||
window: tcpWindow,
|
window: tcpWindow,
|
||||||
addresses: options.Addresses,
|
handle: handler.HandleConnection,
|
||||||
verify: options.Remote,
|
|
||||||
stamps: options.Remote,
|
|
||||||
conns: make(map[flow]*tcpConn),
|
conns: make(map[flow]*tcpConn),
|
||||||
sockets: make(map[netip.AddrPort]*udpSocket),
|
|
||||||
}
|
}
|
||||||
if t, ok := options.Tun.(segmenter); ok && t.Segments() {
|
if t, ok := options.Tun.(segmenter); ok && t.Segments() {
|
||||||
s.segmenter = t
|
s.segmenter = t
|
||||||
s.window = tcpLargeWindow
|
s.window = tcpLargeWindow
|
||||||
}
|
}
|
||||||
if options.Remote {
|
|
||||||
s.window = tcpLargeWindow
|
|
||||||
}
|
|
||||||
for i := range s.identification {
|
|
||||||
s.identification[i].Store(rand.Uint32())
|
|
||||||
}
|
|
||||||
if handler != nil {
|
|
||||||
s.handle = handler.HandleConnection
|
|
||||||
s.udp = newUdpConnectionHandler(s.handle, s.writeUDP)
|
s.udp = newUdpConnectionHandler(s.handle, s.writeUDP)
|
||||||
}
|
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,8 +85,6 @@ func (s *stack) Close() error {
|
|||||||
for _, c := range s.conns {
|
for _, c := range s.conns {
|
||||||
conns = append(conns, c)
|
conns = append(conns, c)
|
||||||
}
|
}
|
||||||
sockets := s.sockets
|
|
||||||
s.sockets = nil
|
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
for _, c := range conns {
|
for _, c := range conns {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
@@ -145,94 +93,27 @@ func (s *stack) Close() error {
|
|||||||
}
|
}
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
}
|
}
|
||||||
for _, u := range sockets {
|
|
||||||
u.queue.close()
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// read takes the packets of the tun until it is closed
|
// read takes the packets of the tun until it is closed
|
||||||
func (s *stack) read() {
|
func (s *stack) read() {
|
||||||
var err error
|
// a tun that does not split segments gives them as large as a packet can be
|
||||||
if s.segmenter != nil {
|
|
||||||
err = s.readSegments()
|
|
||||||
} else {
|
|
||||||
// as large as a packet can be, whatever the MTU is
|
|
||||||
b := make([]byte, packetRoom+1<<16)
|
b := make([]byte, packetRoom+1<<16)
|
||||||
for err == nil {
|
for {
|
||||||
var p []byte
|
b, err := s.tun.ReadPacket(b)
|
||||||
if p, err = s.tun.ReadPacket(b); err == nil {
|
if err != nil {
|
||||||
s.Input(p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
closed := s.closed
|
closed := s.closed
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
if !closed {
|
if !closed {
|
||||||
errors.LogErrorInner(s.ctx, err, "[tun] failed to read a packet, no more are read")
|
errors.LogErrorInner(s.ctx, err, "[tun] failed to read a packet, no more are read")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// readSegments is read for a tun that does not split segments and gives them as large as a packet can be.
|
|
||||||
// They are read into buffers for a connection to keep what is large as it is, see keep
|
|
||||||
func (s *stack) readSegments() error {
|
|
||||||
s.bufs = make(buf.MultiBuffer, (1<<16)/buf.Size)
|
|
||||||
defer buf.ReleaseMulti(s.bufs)
|
|
||||||
parts := make([][]byte, len(s.bufs))
|
|
||||||
var r room
|
|
||||||
for {
|
|
||||||
for i, b := range s.bufs {
|
|
||||||
if b == nil {
|
|
||||||
s.bufs[i] = buf.New()
|
|
||||||
_, _ = s.bufs[i].ReadFrom(&r)
|
|
||||||
parts[i] = r.b
|
|
||||||
}
|
|
||||||
}
|
|
||||||
n, err := s.segmenter.ReadParts(parts)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.input(parts[0][:min(n, buf.Size)], max(n-buf.Size, 0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// room takes what it is given to read into as read: that is the room of a Buffer as it is, not cleared
|
|
||||||
type room struct{ b []byte }
|
|
||||||
|
|
||||||
func (r *room) Read(b []byte) (int, error) {
|
|
||||||
r.b = b
|
|
||||||
return len(b), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Input implements Stack. Several tunnels may hand over their packets at once
|
|
||||||
func (s *stack) Input(b []byte) {
|
|
||||||
s.input(b, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// input takes a packet that is b and rest bytes after it, in the bufs that follow the one that b is in
|
|
||||||
func (s *stack) input(b []byte, rest int) {
|
|
||||||
p, ok := parsePacket(b, rest)
|
|
||||||
if !ok || p.src.Is4In6() || p.dst.Is4In6() || p.src.IsLoopback() || p.dst.IsLoopback() || p.dst.IsUnspecified() || p.src.IsMulticast() {
|
|
||||||
// nothing comes from many at once, and an address that stands for this machine itself is not one
|
|
||||||
// that the other side of a tun can ask for. Nor is an IPv6 address that stands for an IPv4 one, which
|
|
||||||
// would be dialed as that: ::ffff:0.0.0.0 is this machine as well
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if s.verify && b[0]>>4 == 4 && checksum(sum(b[:int(b[0]&0xf)*4], 0)) != 0 {
|
p, ok := parsePacket(b)
|
||||||
return
|
if !ok {
|
||||||
}
|
continue
|
||||||
// what was put together may be of two packets with one number, its checksum tells
|
|
||||||
verify := s.verify || p.fragment
|
|
||||||
if p.rest != 0 && (verify || p.proto != protoTCP) {
|
|
||||||
// only a segment of TCP that is not looked at can stay in pieces
|
|
||||||
p.payload, p.rest = s.whole(p.payload, p.rest), 0
|
|
||||||
}
|
|
||||||
if p.fragment {
|
|
||||||
p, ok = s.fragments.add(p)
|
|
||||||
}
|
|
||||||
if !ok || verify && !p.verified() {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
switch p.proto {
|
switch p.proto {
|
||||||
case protoTCP:
|
case protoTCP:
|
||||||
@@ -243,57 +124,6 @@ func (s *stack) input(b []byte, rest int) {
|
|||||||
s.handleICMP(p)
|
s.handleICMP(p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// whole returns in one piece what is b and rest bytes after it, in the bufs that follow the one that b is in
|
|
||||||
func (s *stack) whole(b []byte, rest int) []byte {
|
|
||||||
if rest == 0 {
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
s.joined = append(s.joined[:0], b...)
|
|
||||||
for i := 1; rest > 0; i++ {
|
|
||||||
n := min(rest, buf.Size)
|
|
||||||
s.joined = append(s.joined, s.bufs[i].BytesTo(int32(n))...)
|
|
||||||
rest -= n
|
|
||||||
}
|
|
||||||
return s.joined
|
|
||||||
}
|
|
||||||
|
|
||||||
// keep adds to mb what is b and rest bytes after it, in the bufs that follow the one that b is in: nothing is copied,
|
|
||||||
// the buffers are taken for it and no longer read into
|
|
||||||
func (s *stack) keep(mb buf.MultiBuffer, b []byte, rest int) buf.MultiBuffer {
|
|
||||||
// b ends where what was read into the first one does, or before it
|
|
||||||
first := s.bufs[0]
|
|
||||||
from := first.Cap() - int32(cap(b))
|
|
||||||
first.Resize(from, from+int32(len(b)))
|
|
||||||
mb, s.bufs[0] = append(mb, first), nil
|
|
||||||
for i := 1; rest > 0; i++ {
|
|
||||||
n := min(rest, buf.Size)
|
|
||||||
s.bufs[i].Resize(0, int32(n))
|
|
||||||
mb, s.bufs[i] = append(mb, s.bufs[i]), nil
|
|
||||||
rest -= n
|
|
||||||
}
|
|
||||||
return mb
|
|
||||||
}
|
|
||||||
|
|
||||||
// local returns the address that packets to remote are sent from
|
|
||||||
func (s *stack) local(remote netip.Addr) (netip.Addr, error) {
|
|
||||||
for _, a := range s.addresses {
|
|
||||||
if a.Is4() == remote.Is4() {
|
|
||||||
return a, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return netip.Addr{}, errors.New("no address to reach ", remote, " from")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ephemeral returns a port for what is opened from here: the first that free takes, from a random one on
|
|
||||||
func ephemeral(free func(port uint16) bool) (uint16, bool) {
|
|
||||||
start := rand.Uint32()
|
|
||||||
for i := range uint32(1 << 15) {
|
|
||||||
if port := uint16(1<<15 | (start+i)&(1<<15-1)); free(port) {
|
|
||||||
return port, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0, false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stack) handleTCP(p packet) {
|
func (s *stack) handleTCP(p packet) {
|
||||||
@@ -316,7 +146,6 @@ func (s *stack) handleTCP(p packet) {
|
|||||||
window: binary.BigEndian.Uint16(t[14:]),
|
window: binary.BigEndian.Uint16(t[14:]),
|
||||||
options: t[tcpHeaderSize:size],
|
options: t[tcpHeaderSize:size],
|
||||||
payload: t[size:],
|
payload: t[size:],
|
||||||
rest: p.rest,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
@@ -325,7 +154,7 @@ func (s *stack) handleTCP(p packet) {
|
|||||||
if c != nil && c.input(seg) {
|
if c != nil && c.input(seg) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if seg.flags&(tcpSyn|tcpAck|tcpRst) != tcpSyn || s.handle == nil {
|
if seg.flags&(tcpSyn|tcpAck|tcpRst) != tcpSyn {
|
||||||
if seg.flags&tcpRst == 0 {
|
if seg.flags&tcpRst == 0 {
|
||||||
s.refuse(f, seg)
|
s.refuse(f, seg)
|
||||||
}
|
}
|
||||||
@@ -335,8 +164,7 @@ func (s *stack) handleTCP(p packet) {
|
|||||||
if s.opening.Load() >= tcpOpening {
|
if s.opening.Load() >= tcpOpening {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c = newTCPConn(s, f)
|
c = newTCPConn(s, f, seg)
|
||||||
c.synchronize(seg)
|
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
if s.closed {
|
if s.closed {
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
@@ -346,7 +174,7 @@ func (s *stack) handleTCP(p packet) {
|
|||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
s.opening.Add(1)
|
s.opening.Add(1)
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
c.sendSyn()
|
c.sendSynAck()
|
||||||
c.arm(tcpTick)
|
c.arm(tcpTick)
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
}
|
}
|
||||||
@@ -363,7 +191,7 @@ func (s *stack) refuse(f flow, seg segment) {
|
|||||||
binary.BigEndian.PutUint32(t[4:], seg.ack)
|
binary.BigEndian.PutUint32(t[4:], seg.ack)
|
||||||
t[13] = tcpRst
|
t[13] = tcpRst
|
||||||
} else {
|
} else {
|
||||||
ack := seg.seq + uint32(len(seg.payload)+seg.rest)
|
ack := seg.seq + uint32(len(seg.payload))
|
||||||
if seg.flags&(tcpSyn|tcpFin) != 0 {
|
if seg.flags&(tcpSyn|tcpFin) != 0 {
|
||||||
ack++
|
ack++
|
||||||
}
|
}
|
||||||
@@ -396,189 +224,51 @@ func (s *stack) handleUDP(p packet) {
|
|||||||
if size < udpHeaderSize || size > len(u) {
|
if size < udpHeaderSize || size > len(u) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var socket *udpSocket
|
|
||||||
if s.handle == nil {
|
|
||||||
// only for a port that was opened from here
|
|
||||||
s.mu.Lock()
|
|
||||||
socket = s.sockets[netip.AddrPortFrom(p.dst, binary.BigEndian.Uint16(u[2:]))]
|
|
||||||
s.mu.Unlock()
|
|
||||||
if socket == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// the packet waits for its reader, longer than read keeps it
|
// the packet waits for its reader, longer than read keeps it
|
||||||
b := buf.NewWithSize(int32(size - udpHeaderSize))
|
b := buf.NewWithSize(int32(size - udpHeaderSize))
|
||||||
b.Write(u[udpHeaderSize:size])
|
b.Write(u[udpHeaderSize:size])
|
||||||
src := net.UDPDestination(net.IPAddress(p.src.AsSlice()), net.Port(binary.BigEndian.Uint16(u)))
|
src := net.UDPDestination(net.IPAddress(p.src.AsSlice()), net.Port(binary.BigEndian.Uint16(u)))
|
||||||
if socket == nil {
|
dst := net.UDPDestination(net.IPAddress(p.dst.AsSlice()), net.Port(binary.BigEndian.Uint16(u[2:])))
|
||||||
s.udp.HandlePacket(src, net.UDPDestination(net.IPAddress(p.dst.AsSlice()), net.Port(binary.BigEndian.Uint16(u[2:]))), b)
|
s.udp.HandlePacket(src, dst, b)
|
||||||
} else if b.UDP = &src; !socket.queue.push(b) {
|
|
||||||
b.Release()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeUDP writes a packet with the payload from src to dst to the tun
|
// writeUDP writes a packet with the payload from src to dst to the tun
|
||||||
func (s *stack) writeUDP(payload []byte, src net.Destination, dst net.Destination) error {
|
func (s *stack) writeUDP(payload []byte, src net.Destination, dst net.Destination) error {
|
||||||
srcIP, _ := netip.AddrFromSlice(src.Address.IP())
|
srcIP, _ := netip.AddrFromSlice(src.Address.IP())
|
||||||
dstIP, _ := netip.AddrFromSlice(dst.Address.IP())
|
dstIP, _ := netip.AddrFromSlice(dst.Address.IP())
|
||||||
return s.sendUDP(payload, netip.AddrPortFrom(srcIP, uint16(src.Port)), netip.AddrPortFrom(dstIP, uint16(dst.Port)))
|
srcIP, dstIP = srcIP.Unmap(), dstIP.Unmap()
|
||||||
}
|
if !srcIP.IsValid() || srcIP.Is4() != dstIP.Is4() {
|
||||||
|
return errors.New("failed to write udp packet from ", src, " to ", dst)
|
||||||
func (s *stack) sendUDP(payload []byte, src, dst netip.AddrPort) error {
|
|
||||||
srcIP, dstIP := src.Addr().Unmap(), dst.Addr().Unmap()
|
|
||||||
if !srcIP.IsValid() || srcIP.Is4() != dstIP.Is4() || len(payload) > math.MaxUint16-udpHeaderSize {
|
|
||||||
return errors.New("failed to write udp packet of ", len(payload), " bytes from ", src, " to ", dst)
|
|
||||||
}
|
}
|
||||||
var b [ipv6HeaderSize + udpHeaderSize]byte
|
var b [ipv6HeaderSize + udpHeaderSize]byte
|
||||||
n := putIPHeader(b[:], protoUDP, srcIP, dstIP)
|
n := putIPHeader(b[:], protoUDP, srcIP, dstIP)
|
||||||
u := b[n : n+udpHeaderSize]
|
u := b[n : n+udpHeaderSize]
|
||||||
binary.BigEndian.PutUint16(u, src.Port())
|
binary.BigEndian.PutUint16(u, uint16(src.Port))
|
||||||
binary.BigEndian.PutUint16(u[2:], dst.Port())
|
binary.BigEndian.PutUint16(u[2:], uint16(dst.Port))
|
||||||
binary.BigEndian.PutUint16(u[4:], uint16(udpHeaderSize+len(payload)))
|
binary.BigEndian.PutUint16(u[4:], uint16(udpHeaderSize+len(payload)))
|
||||||
|
setIPLength(b[:n], n+udpHeaderSize+len(payload))
|
||||||
check := transportChecksum(b[:n], protoUDP, u, payload)
|
check := transportChecksum(b[:n], protoUDP, u, payload)
|
||||||
if check == 0 {
|
if check == 0 {
|
||||||
// zero stands for no checksum
|
// zero stands for no checksum
|
||||||
check = 0xffff
|
check = 0xffff
|
||||||
}
|
}
|
||||||
binary.BigEndian.PutUint16(u[6:], check)
|
binary.BigEndian.PutUint16(u[6:], check)
|
||||||
return s.write(b[:n+udpHeaderSize], payload)
|
return s.tun.WritePacket(b[:n+udpHeaderSize], payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
// write completes the IP header of a packet that is in two parts and writes it to the tun, in fragments if it is
|
// handleICMP answers an echo request as its destination would
|
||||||
// larger than the MTU. A segment of a connection never is, and is not written by this
|
|
||||||
func (s *stack) write(header, payload []byte) error {
|
|
||||||
size, last := ipv6HeaderSize, 39
|
|
||||||
if header[0]>>4 == 4 {
|
|
||||||
size, last = ipv4HeaderSize, 19
|
|
||||||
}
|
|
||||||
// the group is chosen by the last bytes of the destination
|
|
||||||
id := s.identification[(header[last]^header[last-1]<<3)%byte(len(s.identification))].Add(1)
|
|
||||||
if header[0]>>4 == 4 {
|
|
||||||
// a link on the way may split it as well, and what is put together there is told apart by its number
|
|
||||||
binary.BigEndian.PutUint16(header[4:], uint16(id))
|
|
||||||
}
|
|
||||||
if len(header)+len(payload) > s.mtu {
|
|
||||||
return writeFragments(s.tun, s.mtu, id, header, payload)
|
|
||||||
}
|
|
||||||
setIPLength(header[:size], len(header)+len(payload))
|
|
||||||
return s.tun.WritePacket(header, payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DialUDP implements Stack
|
|
||||||
func (s *stack) DialUDP() (net.PacketConn, error) {
|
|
||||||
u := &udpSocket{stack: s, queue: newPacketQueue(udpQueueLimit)}
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
if s.closed {
|
|
||||||
return nil, io.ErrClosedPipe
|
|
||||||
}
|
|
||||||
port, ok := ephemeral(func(port uint16) bool {
|
|
||||||
for _, a := range s.addresses {
|
|
||||||
if s.sockets[netip.AddrPortFrom(a, port)] != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
if !ok || len(s.addresses) == 0 {
|
|
||||||
return nil, errors.New("no port left on ", s.addresses)
|
|
||||||
}
|
|
||||||
u.port = port
|
|
||||||
for _, a := range s.addresses {
|
|
||||||
s.sockets[netip.AddrPortFrom(a, port)] = u
|
|
||||||
}
|
|
||||||
return u, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// udpSocket is a UDP port that was opened from here, on every address of the stack. It is not bound to one
|
|
||||||
// other side: what anyone sends to it is read from it, with who sent it
|
|
||||||
type udpSocket struct {
|
|
||||||
stack *stack
|
|
||||||
port uint16
|
|
||||||
queue *packetQueue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *udpSocket) ReadFrom(p []byte) (int, net.Addr, error) {
|
|
||||||
b, ok := u.queue.pop()
|
|
||||||
if !ok {
|
|
||||||
return 0, nil, gonet.ErrClosed
|
|
||||||
}
|
|
||||||
defer b.Release()
|
|
||||||
return copy(p, b.Bytes()), b.UDP.RawNetAddr(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *udpSocket) WriteTo(p []byte, addr net.Addr) (int, error) {
|
|
||||||
to, ok := addr.(*net.UDPAddr)
|
|
||||||
if !ok {
|
|
||||||
return 0, errors.New("not a udp address: ", addr)
|
|
||||||
}
|
|
||||||
local, err := u.stack.local(to.AddrPort().Addr().Unmap())
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
if err := u.stack.sendUDP(p, netip.AddrPortFrom(local, u.port), to.AddrPort()); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return len(p), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *udpSocket) Close() error {
|
|
||||||
u.stack.mu.Lock()
|
|
||||||
for _, a := range u.stack.addresses {
|
|
||||||
if local := netip.AddrPortFrom(a, u.port); u.stack.sockets[local] == u {
|
|
||||||
delete(u.stack.sockets, local)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
u.stack.mu.Unlock()
|
|
||||||
u.queue.close()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// LocalAddr implements net.PacketConn with the first address of the stack
|
|
||||||
func (u *udpSocket) LocalAddr() net.Addr {
|
|
||||||
return gonet.UDPAddrFromAddrPort(netip.AddrPortFrom(u.stack.addresses[0], u.port))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *udpSocket) SetDeadline(time.Time) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *udpSocket) SetReadDeadline(time.Time) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *udpSocket) SetWriteDeadline(time.Time) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleICMP answers an echo request as its destination would, and takes the word that a packet of a connection
|
|
||||||
// was too large for a link on its way or found no way at all
|
|
||||||
func (s *stack) handleICMP(p packet) {
|
func (s *stack) handleICMP(p packet) {
|
||||||
m := p.payload
|
m := p.payload
|
||||||
request, reply := byte(8), byte(0)
|
request, reply := byte(8), byte(0)
|
||||||
if p.proto == protoICMPv6 {
|
if p.proto == protoICMPv6 {
|
||||||
request, reply = 128, 129
|
request, reply = 128, 129
|
||||||
}
|
}
|
||||||
if len(m) < 8 {
|
if len(m) < 8 || m[0] != request || m[1] != 0 {
|
||||||
return
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case p.proto == protoICMPv4 && m[0] == 3 && m[1] == 4:
|
|
||||||
s.undelivered(m[8:], max(int(binary.BigEndian.Uint16(m[6:])), ipv4SmallestMTU))
|
|
||||||
return
|
|
||||||
case p.proto == protoICMPv6 && m[0] == 2:
|
|
||||||
s.undelivered(m[8:], max(int(binary.BigEndian.Uint32(m[4:])), ipv6SmallestMTU))
|
|
||||||
return
|
|
||||||
case p.proto == protoICMPv4 && m[0] == 3, p.proto == protoICMPv6 && m[0] == 1:
|
|
||||||
s.undelivered(m[8:], 0)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if m[0] != request || m[1] != 0 || s.handle == nil && !slices.Contains(s.addresses, p.dst) {
|
|
||||||
// a stack that takes no connections is one host, not all of them
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var b [ipv6HeaderSize]byte
|
var b [ipv6HeaderSize]byte
|
||||||
n := putIPHeader(b[:], p.proto, p.dst, p.src)
|
n := putIPHeader(b[:], p.proto, p.dst, p.src)
|
||||||
|
setIPLength(b[:n], n+len(m))
|
||||||
// the reply is the request with another type
|
// the reply is the request with another type
|
||||||
m[0], m[2], m[3] = reply, 0, 0
|
m[0], m[2], m[3] = reply, 0, 0
|
||||||
if p.proto == protoICMPv6 {
|
if p.proto == protoICMPv6 {
|
||||||
@@ -586,39 +276,5 @@ func (s *stack) handleICMP(p packet) {
|
|||||||
} else {
|
} else {
|
||||||
binary.BigEndian.PutUint16(m[2:], checksum(sum(m, 0)))
|
binary.BigEndian.PutUint16(m[2:], checksum(sum(m, 0)))
|
||||||
}
|
}
|
||||||
_ = s.write(b[:n], m)
|
_ = s.tun.WritePacket(b[:n], m)
|
||||||
}
|
|
||||||
|
|
||||||
// undelivered takes the start of a packet that was sent from here and did not get to where it goes. If it did not fit
|
|
||||||
// a link on its way, which takes packets of mtu bytes, the connection that sent it goes on with smaller segments.
|
|
||||||
// Without an mtu there is no way at all, and a connection that is being opened is not waited for
|
|
||||||
func (s *stack) undelivered(b []byte, mtu int) {
|
|
||||||
var proto byte
|
|
||||||
var local, remote netip.Addr
|
|
||||||
switch {
|
|
||||||
case len(b) >= ipv4HeaderSize && b[0]>>4 == 4:
|
|
||||||
proto, local, remote = b[9], netip.AddrFrom4([4]byte(b[12:16])), netip.AddrFrom4([4]byte(b[16:20]))
|
|
||||||
b = b[min(int(b[0]&0xf)*4, len(b)):]
|
|
||||||
case len(b) >= ipv6HeaderSize && b[0]>>4 == 6:
|
|
||||||
proto, local, remote = b[6], netip.AddrFrom16([16]byte(b[8:24])), netip.AddrFrom16([16]byte(b[24:40]))
|
|
||||||
b = b[ipv6HeaderSize:]
|
|
||||||
}
|
|
||||||
// of the segment there are the ports and the sequence number at least
|
|
||||||
if proto != protoTCP || len(b) < 8 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.mu.Lock()
|
|
||||||
c := s.conns[flow{
|
|
||||||
src: netip.AddrPortFrom(remote, binary.BigEndian.Uint16(b[2:])),
|
|
||||||
dst: netip.AddrPortFrom(local, binary.BigEndian.Uint16(b)),
|
|
||||||
}]
|
|
||||||
s.mu.Unlock()
|
|
||||||
if c == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if seq := binary.BigEndian.Uint32(b[4:]); mtu != 0 {
|
|
||||||
c.shrink(seq, mtu)
|
|
||||||
} else {
|
|
||||||
c.unreachable(seq)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+144
-1556
File diff suppressed because it is too large
Load Diff
+41
-303
@@ -1,12 +1,10 @@
|
|||||||
package tun
|
package tun
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
"math/rand/v2"
|
"math/rand/v2"
|
||||||
"net/netip"
|
|
||||||
"slices"
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -39,9 +37,7 @@ const (
|
|||||||
tcpHeld = 1024
|
tcpHeld = 1024
|
||||||
// tcpSmallest is the smallest mss that the system is believed, the one of Linux
|
// tcpSmallest is the smallest mss that the system is believed, the one of Linux
|
||||||
tcpSmallest = 48
|
tcpSmallest = 48
|
||||||
// tcpStamps is what the times of both sides take of the options of every segment that carries them (RFC 7323)
|
// tcpOpening is how many connections can be waiting for the answer to their SYN-ACK
|
||||||
tcpStamps = 12
|
|
||||||
// tcpOpening is how many connections that the other side opens can be waiting for the answer to their SYN-ACK
|
|
||||||
tcpOpening = 65535
|
tcpOpening = 65535
|
||||||
// tcpStart is how many segments are sent before any of them is acknowledged
|
// tcpStart is how many segments are sent before any of them is acknowledged
|
||||||
tcpStart = 10
|
tcpStart = 10
|
||||||
@@ -49,24 +45,15 @@ const (
|
|||||||
// tcpParts of how many parts of what was written it is made at most
|
// tcpParts of how many parts of what was written it is made at most
|
||||||
tcpSegment = math.MaxUint16 - ipv6HeaderSize - tcpHeaderSize
|
tcpSegment = math.MaxUint16 - ipv6HeaderSize - tcpHeaderSize
|
||||||
tcpParts = 7
|
tcpParts = 7
|
||||||
// tcpKept is how large a payload is at least for the buffers that it was read into to be kept. A smaller one
|
|
||||||
// is copied to where the one before it ends, for a reader to get buffers that are full
|
|
||||||
tcpKept = buf.Size / 2
|
|
||||||
|
|
||||||
// tcpTick is how often a connection that waits for something looks at what it waits for
|
// tcpTick is how often a connection that waits for something looks at what it waits for
|
||||||
tcpTick = 100 * time.Millisecond
|
tcpTick = 100 * time.Millisecond
|
||||||
// ticks without anything acknowledged before it is sent again, twice as many every next time, a minute at most,
|
// ticks without anything acknowledged before it is sent again, twice as many every next time, a minute at most
|
||||||
// while an open connection that waits sends again every two seconds at most or as often as an answer takes,
|
|
||||||
// twice as seldom every next minute: when its way was away for a while it goes on soon after the way is back,
|
|
||||||
// and the next segment still waits the doubled time, so that its answer can be timed
|
|
||||||
tcpRetransmitTicks = 3
|
tcpRetransmitTicks = 3
|
||||||
tcpRetransmitTicksOpen = 20
|
|
||||||
tcpRetransmitTicksMost = 600
|
tcpRetransmitTicksMost = 600
|
||||||
tcpMinuteTicks = int(time.Minute / tcpTick)
|
// how many times it is sent again before the connection is given up: that is after four minutes, as what is
|
||||||
// an open connection is given up after four minutes of ticks without an acknowledgment since the timer first sent
|
// behind a wireless link may be away for a while, and after twenty seconds for one that never answered
|
||||||
// it again, as what is behind a wireless link may be away for a while (ticks, so that the time a computer sleeps
|
tcpRetransmits = 10
|
||||||
// is not counted), and one that never answered after it was sent again that many times, after twenty seconds
|
|
||||||
tcpGiveUpTicks = 4 * tcpMinuteTicks
|
|
||||||
tcpSynRetransmits = 5
|
tcpSynRetransmits = 5
|
||||||
// ticks between the probes of a window that stays closed
|
// ticks between the probes of a window that stays closed
|
||||||
tcpProbeTicks = 10
|
tcpProbeTicks = 10
|
||||||
@@ -77,22 +64,15 @@ const (
|
|||||||
var (
|
var (
|
||||||
errTCPReset = errors.New("connection reset")
|
errTCPReset = errors.New("connection reset")
|
||||||
errTCPTimeout = errors.New("connection timed out")
|
errTCPTimeout = errors.New("connection timed out")
|
||||||
errTCPRefused = errors.New("connection refused")
|
|
||||||
errTCPUnreachable = errors.New("destination unreachable")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// tcpStarted is where the times that segments carry count from
|
|
||||||
var tcpStarted = time.Now()
|
|
||||||
|
|
||||||
// segment is a TCP segment that came from the tun
|
// segment is a TCP segment that came from the tun
|
||||||
type segment struct {
|
type segment struct {
|
||||||
seq, ack uint32
|
seq, ack uint32
|
||||||
flags byte
|
flags byte
|
||||||
window uint16
|
window uint16
|
||||||
options []byte
|
options []byte
|
||||||
// payload is without its last rest bytes when those are in other buffers of the stack, see stack.whole
|
|
||||||
payload []byte
|
payload []byte
|
||||||
rest int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// nextOption returns the first option of o and what follows it, kind is zero when there is none
|
// nextOption returns the first option of o and what follows it, kind is zero when there is none
|
||||||
@@ -112,7 +92,7 @@ type held struct {
|
|||||||
data *buf.Buffer
|
data *buf.Buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
// tcpConn is a TCP connection that the system opened through the tun, or one that was opened to it by DialTCP.
|
// tcpConn is a TCP connection that the system opened through the tun.
|
||||||
// Mostly the two are on the same machine, with nothing but the queue of the tun in between, which drops what
|
// Mostly the two are on the same machine, with nothing but the queue of the tun in between, which drops what
|
||||||
// does not fit. But the system may also pass on what others send, as a router does, and then the link to them
|
// does not fit. But the system may also pass on what others send, as a router does, and then the link to them
|
||||||
// is in between. So a connection has windows that are small enough, keeps what came after a lost segment,
|
// is in between. So a connection has windows that are small enough, keeps what came after a lost segment,
|
||||||
@@ -134,19 +114,12 @@ type tcpConn struct {
|
|||||||
sndShift, rcvShift uint8
|
sndShift, rcvShift uint8
|
||||||
// sack is set if the system takes selective acknowledgments
|
// sack is set if the system takes selective acknowledgments
|
||||||
sack bool
|
sack bool
|
||||||
// stamps is set if every segment carries the time of this side and the last one that came of the other side,
|
|
||||||
// which sees by it how long its segments take, also the ones that it sent again (RFC 7323).
|
|
||||||
// stampBase is what the time of this side starts from, stampSent the one that the last segment carried
|
|
||||||
stamps bool
|
|
||||||
stampBase, stampSent uint32
|
|
||||||
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
readable sync.Cond
|
readable sync.Cond
|
||||||
writable sync.Cond
|
writable sync.Cond
|
||||||
|
|
||||||
established bool
|
established bool
|
||||||
// opened is there while DialTCP waits for the answer to its SYN, and closed when that wait is over
|
|
||||||
opened chan struct{}
|
|
||||||
// closed is set when Close is called
|
// closed is set when Close is called
|
||||||
closed bool
|
closed bool
|
||||||
// err is why the connection is gone
|
// err is why the connection is gone
|
||||||
@@ -167,8 +140,6 @@ type tcpConn struct {
|
|||||||
rcvHeld []held
|
rcvHeld []held
|
||||||
// rcvLast is the seq of the last one that was put into rcvHeld
|
// rcvLast is the seq of the last one that was put into rcvHeld
|
||||||
rcvLast uint32
|
rcvLast uint32
|
||||||
// stamp is the time of the other side that is sent back to it
|
|
||||||
stamp uint32
|
|
||||||
|
|
||||||
sndUna, sndNxt uint32
|
sndUna, sndNxt uint32
|
||||||
sndWnd uint32
|
sndWnd uint32
|
||||||
@@ -194,9 +165,6 @@ type tcpConn struct {
|
|||||||
recover uint32
|
recover uint32
|
||||||
// lost is the end of what is lost past the last of sacked, resent the end of what was sent again since then
|
// lost is the end of what is lost past the last of sacked, resent the end of what was sent again since then
|
||||||
lost, resent uint32
|
lost, resent uint32
|
||||||
// headAt is when the segment at sndUna was sent again, or took that place while something was lost.
|
|
||||||
// It is zero when that segment is not sent once more for the system still asking for it: it was already
|
|
||||||
headAt time.Time
|
|
||||||
// timed is the end of a segment that was sent at timedAt and not again, to see how long its acknowledgment takes.
|
// timed is the end of a segment that was sent at timedAt and not again, to see how long its acknowledgment takes.
|
||||||
// The first one is the SYN-ACK
|
// The first one is the SYN-ACK
|
||||||
timed uint32
|
timed uint32
|
||||||
@@ -208,17 +176,17 @@ type tcpConn struct {
|
|||||||
armed bool
|
armed bool
|
||||||
// stalled counts ticks without an acknowledgment of what waits for one
|
// stalled counts ticks without an acknowledgment of what waits for one
|
||||||
stalled, retransmitTicks, retransmits int
|
stalled, retransmitTicks, retransmits int
|
||||||
// lostTicks counts the ticks since the timer sent again the first time since something was acknowledged
|
|
||||||
lostTicks int
|
|
||||||
lingered bool
|
lingered bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTCPConn(s *stack, f flow) *tcpConn {
|
func newTCPConn(s *stack, f flow, syn segment) *tcpConn {
|
||||||
c := &tcpConn{
|
c := &tcpConn{
|
||||||
stack: s,
|
stack: s,
|
||||||
flow: f,
|
flow: f,
|
||||||
src: net.TCPDestination(net.IPAddress(f.src.Addr().AsSlice()), net.Port(f.src.Port())),
|
src: net.TCPDestination(net.IPAddress(f.src.Addr().AsSlice()), net.Port(f.src.Port())),
|
||||||
dst: net.TCPDestination(net.IPAddress(f.dst.Addr().AsSlice()), net.Port(f.dst.Port())),
|
dst: net.TCPDestination(net.IPAddress(f.dst.Addr().AsSlice()), net.Port(f.dst.Port())),
|
||||||
|
rcvNxt: syn.seq + 1,
|
||||||
|
sndWnd: uint32(syn.window),
|
||||||
window: s.window,
|
window: s.window,
|
||||||
ssthresh: uint32(s.window),
|
ssthresh: uint32(s.window),
|
||||||
timedAt: time.Now(),
|
timedAt: time.Now(),
|
||||||
@@ -228,17 +196,6 @@ func newTCPConn(s *stack, f flow) *tcpConn {
|
|||||||
c.writable.L = &c.mu
|
c.writable.L = &c.mu
|
||||||
c.ipSize = putIPHeader(c.ip[:], protoTCP, f.dst.Addr(), f.src.Addr())
|
c.ipSize = putIPHeader(c.ip[:], protoTCP, f.dst.Addr(), f.src.Addr())
|
||||||
c.mss = s.mtu - c.ipSize - tcpHeaderSize
|
c.mss = s.mtu - c.ipSize - tcpHeaderSize
|
||||||
c.sndUna = rand.Uint32()
|
|
||||||
c.sndNxt = c.sndUna + 1
|
|
||||||
c.stampBase = rand.Uint32()
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// synchronize takes what the SYN of the other side says. c.mu is held, or nobody else has c yet
|
|
||||||
func (c *tcpConn) synchronize(syn segment) {
|
|
||||||
c.rcvNxt = syn.seq + 1
|
|
||||||
c.sndWnd = uint32(syn.window)
|
|
||||||
c.sack, c.stamps, c.sndShift, c.rcvShift = false, false, 0, 0
|
|
||||||
for kind, value, o := nextOption(syn.options); kind != 0; kind, value, o = nextOption(o) {
|
for kind, value, o := nextOption(syn.options); kind != 0; kind, value, o = nextOption(o) {
|
||||||
switch {
|
switch {
|
||||||
case kind == 2 && len(value) == 2:
|
case kind == 2 && len(value) == 2:
|
||||||
@@ -248,77 +205,20 @@ func (c *tcpConn) synchronize(syn segment) {
|
|||||||
c.rcvShift = tcpWindowShift
|
c.rcvShift = tcpWindowShift
|
||||||
case kind == 4:
|
case kind == 4:
|
||||||
c.sack = true
|
c.sack = true
|
||||||
case kind == 8 && len(value) == 8 && c.stack.stamps:
|
|
||||||
c.stamps, c.stamp = true, binary.BigEndian.Uint32(value)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if c.stamps {
|
|
||||||
c.mss -= tcpStamps
|
|
||||||
}
|
|
||||||
c.mss = max(c.mss, tcpSmallest)
|
c.mss = max(c.mss, tcpSmallest)
|
||||||
c.cwnd = tcpStart * uint32(c.mss)
|
c.cwnd = tcpStart * uint32(c.mss)
|
||||||
}
|
c.sndUna = rand.Uint32()
|
||||||
|
c.sndNxt = c.sndUna + 1
|
||||||
// DialTCP implements Stack
|
return c
|
||||||
func (s *stack) DialTCP(ctx context.Context, remote netip.AddrPort) (net.Conn, error) {
|
|
||||||
remote = netip.AddrPortFrom(remote.Addr().Unmap(), remote.Port())
|
|
||||||
local, err := s.local(remote.Addr())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
opened := make(chan struct{})
|
|
||||||
s.mu.Lock()
|
|
||||||
if s.closed {
|
|
||||||
s.mu.Unlock()
|
|
||||||
return nil, io.ErrClosedPipe
|
|
||||||
}
|
|
||||||
f := flow{src: remote}
|
|
||||||
port, ok := ephemeral(func(port uint16) bool {
|
|
||||||
f.dst = netip.AddrPortFrom(local, port)
|
|
||||||
return s.conns[f] == nil
|
|
||||||
})
|
|
||||||
if !ok {
|
|
||||||
s.mu.Unlock()
|
|
||||||
return nil, errors.New("no port left to reach ", remote, " from")
|
|
||||||
}
|
|
||||||
f.dst = netip.AddrPortFrom(local, port)
|
|
||||||
c := newTCPConn(s, f)
|
|
||||||
// what the SYN offers, until the answer says what of it is taken
|
|
||||||
c.sack, c.stamps, c.rcvShift, c.opened = true, s.stamps, tcpWindowShift, opened
|
|
||||||
s.conns[f] = c
|
|
||||||
s.mu.Unlock()
|
|
||||||
s.opening.Add(1)
|
|
||||||
c.mu.Lock()
|
|
||||||
if c.err == nil {
|
|
||||||
c.sendSyn()
|
|
||||||
c.arm(tcpTick)
|
|
||||||
}
|
|
||||||
c.mu.Unlock()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-opened:
|
|
||||||
case <-ctx.Done():
|
|
||||||
}
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
if !c.established {
|
|
||||||
if c.err == nil {
|
|
||||||
c.abort(ctx.Err())
|
|
||||||
}
|
|
||||||
return nil, c.err
|
|
||||||
}
|
|
||||||
return c, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// header writes the headers of a segment with size bytes of payload into b and returns their size.
|
// header writes the headers of a segment with size bytes of payload into b and returns their size.
|
||||||
// The checksum of the segment is left to write. c.mu is held
|
// The checksum of the segment is left to write. c.mu is held
|
||||||
func (c *tcpConn) header(b []byte, seq uint32, flags byte, options []byte, size int) int {
|
func (c *tcpConn) header(b []byte, seq uint32, flags byte, options []byte, size int) int {
|
||||||
n := copy(b, c.ip[:c.ipSize])
|
n := copy(b, c.ip[:c.ipSize])
|
||||||
stamps := 0
|
t := b[n : n+tcpHeaderSize+len(options)]
|
||||||
if c.stamps {
|
|
||||||
stamps = tcpStamps
|
|
||||||
}
|
|
||||||
t := b[n : n+tcpHeaderSize+stamps+len(options)]
|
|
||||||
binary.BigEndian.PutUint16(t, c.flow.dst.Port())
|
binary.BigEndian.PutUint16(t, c.flow.dst.Port())
|
||||||
binary.BigEndian.PutUint16(t[2:], c.flow.src.Port())
|
binary.BigEndian.PutUint16(t[2:], c.flow.src.Port())
|
||||||
binary.BigEndian.PutUint32(t[4:], seq)
|
binary.BigEndian.PutUint32(t[4:], seq)
|
||||||
@@ -333,18 +233,7 @@ func (c *tcpConn) header(b []byte, seq uint32, flags byte, options []byte, size
|
|||||||
binary.BigEndian.PutUint16(t[14:], uint16(min(window>>c.rcvShift, math.MaxUint16)))
|
binary.BigEndian.PutUint16(t[14:], uint16(min(window>>c.rcvShift, math.MaxUint16)))
|
||||||
}
|
}
|
||||||
t[16], t[17], t[18], t[19] = 0, 0, 0, 0
|
t[16], t[17], t[18], t[19] = 0, 0, 0, 0
|
||||||
if c.stamps {
|
copy(t[tcpHeaderSize:], options)
|
||||||
o := t[tcpHeaderSize:]
|
|
||||||
o[0], o[1], o[2], o[3] = 1, 1, 8, 10
|
|
||||||
if !c.sending {
|
|
||||||
// what is sent while WriteMultiBuffer writes a segment has the time of that one: it may get to the tun
|
|
||||||
// first, and the other side drops a segment with a time before the last one that it took
|
|
||||||
c.stampSent = c.stampBase + uint32(time.Since(tcpStarted)/time.Millisecond)
|
|
||||||
}
|
|
||||||
binary.BigEndian.PutUint32(o[4:], c.stampSent)
|
|
||||||
binary.BigEndian.PutUint32(o[8:], c.stamp)
|
|
||||||
}
|
|
||||||
copy(t[tcpHeaderSize+stamps:], options)
|
|
||||||
setIPLength(b[:n], n+len(t)+size)
|
setIPLength(b[:n], n+len(t)+size)
|
||||||
c.rcvAcked = c.rcvNxt
|
c.rcvAcked = c.rcvNxt
|
||||||
c.rcvAdvertised = window
|
c.rcvAdvertised = window
|
||||||
@@ -365,8 +254,7 @@ func (c *tcpConn) send(seq uint32, flags byte, options, payload []byte) {
|
|||||||
c.write(b[:c.header(b[:], seq, flags, options, len(payload))], payload)
|
c.write(b[:c.header(b[:], seq, flags, options, len(payload))], payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
// sendSyn sends the answer to the SYN of the other side, or the SYN of a connection that is opened from here
|
func (c *tcpConn) sendSynAck() {
|
||||||
func (c *tcpConn) sendSyn() {
|
|
||||||
options := [12]byte{2, 4}
|
options := [12]byte{2, 4}
|
||||||
binary.BigEndian.PutUint16(options[2:], uint16(c.stack.mtu-c.ipSize-tcpHeaderSize))
|
binary.BigEndian.PutUint16(options[2:], uint16(c.stack.mtu-c.ipSize-tcpHeaderSize))
|
||||||
n := 4
|
n := 4
|
||||||
@@ -376,11 +264,7 @@ func (c *tcpConn) sendSyn() {
|
|||||||
if c.rcvShift != 0 {
|
if c.rcvShift != 0 {
|
||||||
n += copy(options[n:], []byte{1, 3, 3, c.rcvShift})
|
n += copy(options[n:], []byte{1, 3, 3, c.rcvShift})
|
||||||
}
|
}
|
||||||
flags := byte(tcpSyn | tcpAck)
|
c.send(c.sndUna, tcpSyn|tcpAck, options[:n], nil)
|
||||||
if c.opened != nil {
|
|
||||||
flags = tcpSyn
|
|
||||||
}
|
|
||||||
c.send(c.sndUna, flags, options[:n], nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// sendAck acknowledges what was received, and tells what of rcvHeld waits for a lost segment
|
// sendAck acknowledges what was received, and tells what of rcvHeld waits for a lost segment
|
||||||
@@ -394,12 +278,8 @@ func (c *tcpConn) sendAck() {
|
|||||||
c.send(c.sndNxt, tcpAck, nil, nil)
|
c.send(c.sndNxt, tcpAck, nil, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// the block with the segment that came last goes first. Next to the times there is room for one less
|
// the block with the segment that came last goes first
|
||||||
var room [4][2]uint32
|
var blocks [4][2]uint32
|
||||||
blocks := room[:]
|
|
||||||
if c.stamps {
|
|
||||||
blocks = room[:3]
|
|
||||||
}
|
|
||||||
n, last := 1, false
|
n, last := 1, false
|
||||||
for i := 0; i < len(c.rcvHeld); {
|
for i := 0; i < len(c.rcvHeld); {
|
||||||
start, end, recent := c.rcvHeld[i].seq, c.rcvHeld[i].seq, false
|
start, end, recent := c.rcvHeld[i].seq, c.rcvHeld[i].seq, false
|
||||||
@@ -426,22 +306,6 @@ func (c *tcpConn) sendAck() {
|
|||||||
c.send(c.sndNxt, tcpAck, options[:4+8*len(list)], nil)
|
c.send(c.sndNxt, tcpAck, options[:4+8*len(list)], nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// stamped keeps the time that a segment of the other side carries, to send it back. It is the one of the segment
|
|
||||||
// that the stream goes on with, so that the other side sees how long that one took, and not one of what came
|
|
||||||
// before it. c.mu is held
|
|
||||||
func (c *tcpConn) stamped(s segment) {
|
|
||||||
if int32(s.seq-c.rcvAcked) > 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for kind, value, o := nextOption(s.options); kind != 0; kind, value, o = nextOption(o) {
|
|
||||||
if kind == 8 && len(value) == 8 {
|
|
||||||
if stamp := binary.BigEndian.Uint32(value); int32(stamp-c.stamp) > 0 {
|
|
||||||
c.stamp = stamp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// input takes a segment of the connection. It returns false if the connection is gone
|
// input takes a segment of the connection. It returns false if the connection is gone
|
||||||
// and the segment is the start of another one
|
// and the segment is the start of another one
|
||||||
func (c *tcpConn) input(s segment) bool {
|
func (c *tcpConn) input(s segment) bool {
|
||||||
@@ -450,32 +314,6 @@ func (c *tcpConn) input(s segment) bool {
|
|||||||
if c.err != nil {
|
if c.err != nil {
|
||||||
return s.flags&(tcpSyn|tcpAck|tcpRst) != tcpSyn
|
return s.flags&(tcpSyn|tcpAck|tcpRst) != tcpSyn
|
||||||
}
|
}
|
||||||
if c.opened != nil {
|
|
||||||
// only the answer to the SYN is waited for, or the word that nobody takes it
|
|
||||||
switch {
|
|
||||||
case s.flags&tcpAck == 0:
|
|
||||||
case s.ack != c.sndNxt:
|
|
||||||
// of a connection that the other side still has from before: it is told that there is none
|
|
||||||
if s.flags&tcpRst == 0 {
|
|
||||||
c.stack.refuse(c.flow, s)
|
|
||||||
}
|
|
||||||
case s.flags&tcpRst != 0:
|
|
||||||
c.abort(errTCPRefused)
|
|
||||||
case s.flags&tcpSyn != 0:
|
|
||||||
c.synchronize(s)
|
|
||||||
c.established = true
|
|
||||||
c.sndUna, c.sndBase = s.ack, s.ack
|
|
||||||
c.stalled, c.retransmits = 0, 0
|
|
||||||
if !c.timedAt.IsZero() {
|
|
||||||
c.clocked()
|
|
||||||
}
|
|
||||||
c.stack.opening.Add(-1)
|
|
||||||
close(c.opened)
|
|
||||||
c.opened = nil
|
|
||||||
c.sendAck()
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if s.flags&tcpRst != 0 {
|
if s.flags&tcpRst != 0 {
|
||||||
// from where the system is, or was before its FIN, not from someone who knows no more than the addresses
|
// from where the system is, or was before its FIN, not from someone who knows no more than the addresses
|
||||||
if s.seq-c.rcvNxt <= uint32(c.window) || c.rcvFin && s.seq+1 == c.rcvNxt {
|
if s.seq-c.rcvNxt <= uint32(c.window) || c.rcvFin && s.seq+1 == c.rcvNxt {
|
||||||
@@ -486,12 +324,9 @@ func (c *tcpConn) input(s segment) bool {
|
|||||||
if s.flags&tcpSyn != 0 {
|
if s.flags&tcpSyn != 0 {
|
||||||
switch {
|
switch {
|
||||||
case !c.established && s.seq+1 == c.rcvNxt:
|
case !c.established && s.seq+1 == c.rcvNxt:
|
||||||
c.sendSyn()
|
c.sendSynAck()
|
||||||
c.timedAt = time.Time{}
|
c.timedAt = time.Time{}
|
||||||
case c.closed && s.flags&tcpAck == 0 && !c.stack.verify:
|
case c.closed && s.flags&tcpAck == 0:
|
||||||
// the system of this machine opens the same ports again: it has let the old connection go.
|
|
||||||
// What comes through a tunnel is answered with what is expected instead, as anyone who knows
|
|
||||||
// the addresses could send it, and the other side answers that with a reset if it is gone
|
|
||||||
c.abort(errTCPReset)
|
c.abort(errTCPReset)
|
||||||
return false
|
return false
|
||||||
default:
|
default:
|
||||||
@@ -502,9 +337,6 @@ func (c *tcpConn) input(s segment) bool {
|
|||||||
if s.flags&tcpAck == 0 {
|
if s.flags&tcpAck == 0 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if c.stamps {
|
|
||||||
c.stamped(s)
|
|
||||||
}
|
|
||||||
if !c.established {
|
if !c.established {
|
||||||
if s.ack != c.sndNxt {
|
if s.ack != c.sndNxt {
|
||||||
return true
|
return true
|
||||||
@@ -553,7 +385,6 @@ func (c *tcpConn) acknowledge(s segment) {
|
|||||||
if !c.timedAt.IsZero() && int32(c.sndUna-c.timed) >= 0 {
|
if !c.timedAt.IsZero() && int32(c.sndUna-c.timed) >= 0 {
|
||||||
c.clocked()
|
c.clocked()
|
||||||
}
|
}
|
||||||
recovering := c.recovering
|
|
||||||
switch {
|
switch {
|
||||||
case c.recovering && int32(c.sndUna-c.recover) >= 0:
|
case c.recovering && int32(c.sndUna-c.recover) >= 0:
|
||||||
c.recovering = false
|
c.recovering = false
|
||||||
@@ -562,18 +393,11 @@ func (c *tcpConn) acknowledge(s segment) {
|
|||||||
if end := c.sndUna + min(uint32(c.mss), c.recover-c.sndUna); int32(end-c.lost) > 0 {
|
if end := c.sndUna + min(uint32(c.mss), c.recover-c.sndUna); int32(end-c.lost) > 0 {
|
||||||
c.lost = end
|
c.lost = end
|
||||||
}
|
}
|
||||||
c.headAt = time.Now()
|
|
||||||
}
|
}
|
||||||
// the way takes more if what it was given has arrived, as long as it was given about as much as it takes
|
// the way takes more if what it was given has arrived, as long as it was given about as much as it takes
|
||||||
if sent >= c.cwnd/2 {
|
if sent >= c.cwnd/2 {
|
||||||
if c.cwnd < c.ssthresh {
|
if c.cwnd < c.ssthresh {
|
||||||
grow := acked
|
c.cwnd = min(c.cwnd+acked, c.ssthresh)
|
||||||
if recovering {
|
|
||||||
// after a timeout: one segment that is sent again can acknowledge all that came after it,
|
|
||||||
// which says nothing about how much the way takes at once
|
|
||||||
grow = min(grow, 2*uint32(c.mss))
|
|
||||||
}
|
|
||||||
c.cwnd = min(c.cwnd+grow, c.ssthresh)
|
|
||||||
} else if !c.recovering {
|
} else if !c.recovering {
|
||||||
// a segment more for every cwnd that arrives
|
// a segment more for every cwnd that arrives
|
||||||
c.cwnd = min(c.cwnd+max(uint32(uint64(c.mss)*uint64(acked)/uint64(c.cwnd)), 1), uint32(c.window))
|
c.cwnd = min(c.cwnd+max(uint32(uint64(c.mss)*uint64(acked)/uint64(c.cwnd)), 1), uint32(c.window))
|
||||||
@@ -593,22 +417,12 @@ func (c *tcpConn) acknowledge(s segment) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !c.recovering && c.sndUna != c.sndNxt && (len(c.sacked) > 0 || !c.sack && c.dupAcks >= 3) {
|
if !c.recovering && c.sndUna != c.sndNxt && (len(c.sacked) > 0 || c.dupAcks >= 3) {
|
||||||
// the system has something past sndUna, or says the same for the third time when it cannot say what it has:
|
// the system has something past sndUna, or says the same for the third time: the segment at sndUna is lost
|
||||||
// the segment at sndUna is lost. One that can say it repeats itself also for what it got twice
|
|
||||||
c.congested()
|
c.congested()
|
||||||
c.lost = c.sndUna + min(uint32(c.mss), c.sndNxt-c.sndUna)
|
c.lost = c.sndUna + min(uint32(c.mss), c.sndNxt-c.sndUna)
|
||||||
}
|
}
|
||||||
if c.recovering {
|
if c.recovering {
|
||||||
if acked == 0 && c.srtt != 0 && !c.headAt.IsZero() && time.Since(c.headAt) > max(c.srtt+max(c.srtt/4, 4*c.rttvar), tcpTick/4) {
|
|
||||||
// the segment that the system waits for was sent again longer ago than an answer takes, and the system
|
|
||||||
// still answers what comes after it: that one was lost as well. It is sent once more, and only once:
|
|
||||||
// the timer would find it too, but late, and then nothing is known anymore of how much the way takes
|
|
||||||
if start, end := c.hole(c.sndUna); start == c.sndUna && start != end {
|
|
||||||
c.retransmit(start, start+min(end-start, uint32(c.mss)))
|
|
||||||
c.headAt, c.stalled = time.Time{}, 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.resend()
|
c.resend()
|
||||||
}
|
}
|
||||||
c.sndWnd = window
|
c.sndWnd = window
|
||||||
@@ -637,12 +451,7 @@ func (c *tcpConn) clocked() {
|
|||||||
c.rttvar += ((c.srtt - d).Abs() - c.rttvar) / 4
|
c.rttvar += ((c.srtt - d).Abs() - c.rttvar) / 4
|
||||||
c.srtt += (d - c.srtt) / 8
|
c.srtt += (d - c.srtt) / 8
|
||||||
}
|
}
|
||||||
c.retransmitTicks = c.answerTicks()
|
c.retransmitTicks = max(tcpRetransmitTicks, int((c.srtt+4*c.rttvar)/tcpTick)+2)
|
||||||
}
|
|
||||||
|
|
||||||
// answerTicks is how many ticks an acknowledgment is waited for as long as nothing was sent again. c.mu is held
|
|
||||||
func (c *tcpConn) answerTicks() int {
|
|
||||||
return max(tcpRetransmitTicks, int((c.srtt+4*c.rttvar)/tcpTick)+2)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// sackAdd puts what the system says it has into sacked. c.mu is held
|
// sackAdd puts what the system says it has into sacked. c.mu is held
|
||||||
@@ -672,7 +481,7 @@ func (c *tcpConn) congested() {
|
|||||||
c.ssthresh = max(min(c.cwnd, c.sndNxt-c.sndUna)/2, 2*uint32(c.mss))
|
c.ssthresh = max(min(c.cwnd, c.sndNxt-c.sndUna)/2, 2*uint32(c.mss))
|
||||||
c.cwnd = c.ssthresh
|
c.cwnd = c.ssthresh
|
||||||
c.recovering, c.recover, c.resent = true, c.sndNxt, c.sndUna
|
c.recovering, c.recover, c.resent = true, c.sndNxt, c.sndUna
|
||||||
c.timedAt, c.headAt = time.Time{}, time.Time{}
|
c.timedAt = time.Time{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// hole returns the first part at or after seq of what is lost: what the system does not have
|
// hole returns the first part at or after seq of what is lost: what the system does not have
|
||||||
@@ -734,10 +543,6 @@ func (c *tcpConn) resend() {
|
|||||||
room = c.cwnd - flight
|
room = c.cwnd - flight
|
||||||
}
|
}
|
||||||
end = start + min(end-start, room)
|
end = start + min(end-start, room)
|
||||||
if start == c.sndUna {
|
|
||||||
// its answer is waited for from now on
|
|
||||||
c.headAt, c.stalled = time.Now(), 0
|
|
||||||
}
|
|
||||||
c.retransmit(start, end)
|
c.retransmit(start, end)
|
||||||
c.resent = end
|
c.resent = end
|
||||||
}
|
}
|
||||||
@@ -754,12 +559,6 @@ func (c *tcpConn) space() uint32 {
|
|||||||
|
|
||||||
// receive takes the payload of a segment. c.mu is held
|
// receive takes the payload of a segment. c.mu is held
|
||||||
func (c *tcpConn) receive(s segment) {
|
func (c *tcpConn) receive(s segment) {
|
||||||
// what is large, the next and all taken stays in the buffers that it was read into
|
|
||||||
size := len(s.payload) + s.rest
|
|
||||||
keep := c.stack.bufs != nil && size >= tcpKept && s.seq == c.rcvNxt && size <= int(c.window-c.rcvLen)
|
|
||||||
if !keep {
|
|
||||||
s.payload = c.stack.whole(s.payload, s.rest)
|
|
||||||
}
|
|
||||||
seq, payload, fin := s.seq, s.payload, s.flags&tcpFin != 0
|
seq, payload, fin := s.seq, s.payload, s.flags&tcpFin != 0
|
||||||
if len(payload) == 0 && !fin {
|
if len(payload) == 0 && !fin {
|
||||||
if int32(c.rcvNxt-seq) > 0 {
|
if int32(c.rcvNxt-seq) > 0 {
|
||||||
@@ -800,14 +599,9 @@ func (c *tcpConn) receive(s segment) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if keep {
|
|
||||||
c.rcvQueue = c.stack.keep(c.rcvQueue, payload, s.rest)
|
|
||||||
} else {
|
|
||||||
size = len(payload)
|
|
||||||
c.rcvQueue = buf.MergeBytes(c.rcvQueue, payload)
|
c.rcvQueue = buf.MergeBytes(c.rcvQueue, payload)
|
||||||
}
|
c.rcvLen += int32(len(payload))
|
||||||
c.rcvLen += int32(size)
|
c.rcvNxt += uint32(len(payload))
|
||||||
c.rcvNxt += uint32(size)
|
|
||||||
for !fin && len(c.rcvHeld) > 0 && int32(c.rcvNxt-c.rcvHeld[0].seq) >= 0 {
|
for !fin && len(c.rcvHeld) > 0 && int32(c.rcvNxt-c.rcvHeld[0].seq) >= 0 {
|
||||||
h := c.rcvHeld[0]
|
h := c.rcvHeld[0]
|
||||||
if before := int32(c.rcvNxt - h.seq); before < h.data.Len() {
|
if before := int32(c.rcvNxt - h.seq); before < h.data.Len() {
|
||||||
@@ -825,7 +619,7 @@ func (c *tcpConn) receive(s segment) {
|
|||||||
c.readable.Signal()
|
c.readable.Signal()
|
||||||
// the reader acknowledges what it takes, with one segment for all that came by then.
|
// the reader acknowledges what it takes, with one segment for all that came by then.
|
||||||
// It is not waited for when it does not read for long
|
// It is not waited for when it does not read for long
|
||||||
if fin || now || c.rcvNxt-c.rcvAcked >= uint32(c.window/4) {
|
if fin || now || c.rcvNxt-c.rcvAcked >= tcpWindow/4 {
|
||||||
c.sendAck()
|
c.sendAck()
|
||||||
} else {
|
} else {
|
||||||
c.arm(tcpTick)
|
c.arm(tcpTick)
|
||||||
@@ -848,9 +642,9 @@ func (c *tcpConn) release() {
|
|||||||
|
|
||||||
// hold keeps a segment that is not the next one. c.mu is held
|
// hold keeps a segment that is not the next one. c.mu is held
|
||||||
func (c *tcpConn) hold(seq uint32, payload []byte) {
|
func (c *tcpConn) hold(seq uint32, payload []byte) {
|
||||||
i := len(c.rcvHeld)
|
i := 0
|
||||||
for i > 0 && int32(c.rcvHeld[i-1].seq-seq) >= 0 {
|
for i < len(c.rcvHeld) && int32(c.rcvHeld[i].seq-seq) < 0 {
|
||||||
i--
|
i++
|
||||||
}
|
}
|
||||||
// without what the one before and the one after have
|
// without what the one before and the one after have
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
@@ -872,9 +666,8 @@ func (c *tcpConn) hold(seq uint32, payload []byte) {
|
|||||||
if len(payload) == 0 || len(c.rcvHeld) == tcpHeld {
|
if len(payload) == 0 || len(c.rcvHeld) == tcpHeld {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// as much memory as it carries: the room that is left counts bytes, and a thousand tiny segments in pooled
|
b := buf.NewWithSize(int32(len(payload)))
|
||||||
// buffers would take two megabytes
|
b.Write(payload)
|
||||||
b := buf.FromBytes(slices.Clone(payload))
|
|
||||||
c.rcvHeld = slices.Insert(c.rcvHeld, i, held{seq, b})
|
c.rcvHeld = slices.Insert(c.rcvHeld, i, held{seq, b})
|
||||||
c.rcvLast = seq
|
c.rcvLast = seq
|
||||||
}
|
}
|
||||||
@@ -923,38 +716,6 @@ func (c *tcpConn) queued(parts [][]byte, seq uint32, size int) ([][]byte, int) {
|
|||||||
return parts, n
|
return parts, n
|
||||||
}
|
}
|
||||||
|
|
||||||
// shrink makes the segments fit a link on the way that takes packets of mtu bytes, when the one at seq did not.
|
|
||||||
// None of what is on its way fits: it is sent again, the first segment at once and the others as they are
|
|
||||||
// acknowledged. Only what is said about a segment that was sent and is not acknowledged is believed
|
|
||||||
func (c *tcpConn) shrink(seq uint32, mtu int) {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
mss := mtu - c.ipSize - tcpHeaderSize
|
|
||||||
if c.stamps {
|
|
||||||
mss -= tcpStamps
|
|
||||||
}
|
|
||||||
mss = max(mss, tcpSmallest)
|
|
||||||
if c.err != nil || !c.established || seq-c.sndUna >= c.sndNxt-c.sndUna || mss >= c.mss {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.mss = mss
|
|
||||||
// as what is lost, without the way taking less for it: it is the size that it did not take
|
|
||||||
c.recovering, c.recover, c.lost = true, c.sndNxt, c.sndNxt
|
|
||||||
c.timedAt = time.Time{}
|
|
||||||
c.resent = c.sndUna + min(uint32(mss), c.sndNxt-c.sndUna)
|
|
||||||
c.headAt = time.Now()
|
|
||||||
c.retransmit(c.sndUna, c.resent)
|
|
||||||
}
|
|
||||||
|
|
||||||
// unreachable ends the wait for the answer to the SYN at seq, which a router on the way could not deliver
|
|
||||||
func (c *tcpConn) unreachable(seq uint32) {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
if c.err == nil && c.opened != nil && seq == c.sndUna {
|
|
||||||
c.abort(errTCPUnreachable)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// arm makes tick run after d, unless it is going to run already. c.mu is held
|
// arm makes tick run after d, unless it is going to run already. c.mu is held
|
||||||
func (c *tcpConn) arm(d time.Duration) {
|
func (c *tcpConn) arm(d time.Duration) {
|
||||||
if c.armed {
|
if c.armed {
|
||||||
@@ -981,34 +742,19 @@ func (c *tcpConn) tick() {
|
|||||||
}
|
}
|
||||||
switch {
|
switch {
|
||||||
case c.sndUna != c.sndNxt:
|
case c.sndUna != c.sndNxt:
|
||||||
c.stalled++
|
if c.stalled++; c.stalled >= c.retransmitTicks {
|
||||||
due := c.stalled >= c.retransmitTicks
|
if c.retransmits++; c.retransmits > tcpRetransmits || !c.established && c.retransmits > tcpSynRetransmits {
|
||||||
if c.established && c.retransmits > 0 {
|
|
||||||
due = due || c.stalled >= max(tcpRetransmitTicksOpen, c.answerTicks())<<(c.lostTicks/tcpMinuteTicks)
|
|
||||||
}
|
|
||||||
if due {
|
|
||||||
if c.retransmits++; c.retransmits == 1 {
|
|
||||||
c.lostTicks = 0
|
|
||||||
} else {
|
|
||||||
c.lostTicks += c.stalled
|
|
||||||
}
|
|
||||||
if c.established && c.lostTicks > tcpGiveUpTicks || !c.established && c.retransmits > tcpSynRetransmits {
|
|
||||||
c.abort(errTCPTimeout)
|
c.abort(errTCPTimeout)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.stalled = 0
|
c.stalled = 0
|
||||||
c.retransmitTicks = min(c.retransmitTicks*2, tcpRetransmitTicksMost)
|
c.retransmitTicks = min(c.retransmitTicks*2, tcpRetransmitTicksMost)
|
||||||
if !c.established {
|
if !c.established {
|
||||||
c.sendSyn()
|
c.sendSynAck()
|
||||||
c.timedAt = time.Time{}
|
c.timedAt = time.Time{}
|
||||||
} else {
|
} else {
|
||||||
// what the system does not say it has is lost, and the way to it is tried with a segment again.
|
// what the system does not say it has is lost, and the way to it is tried with a segment again
|
||||||
// It takes half of what it took before the loss, not half of that again if the loss was seen already
|
|
||||||
ssthresh, recovering := c.ssthresh, c.recovering
|
|
||||||
c.congested()
|
c.congested()
|
||||||
if recovering {
|
|
||||||
c.ssthresh = ssthresh
|
|
||||||
}
|
|
||||||
c.cwnd, c.lost = uint32(c.mss), c.sndNxt
|
c.cwnd, c.lost = uint32(c.mss), c.sndNxt
|
||||||
c.resend()
|
c.resend()
|
||||||
}
|
}
|
||||||
@@ -1046,10 +792,6 @@ func (c *tcpConn) abort(err error) {
|
|||||||
if c.timer != nil {
|
if c.timer != nil {
|
||||||
c.timer.Stop()
|
c.timer.Stop()
|
||||||
}
|
}
|
||||||
if c.opened != nil {
|
|
||||||
close(c.opened)
|
|
||||||
c.opened = nil
|
|
||||||
}
|
|
||||||
for _, h := range c.rcvHeld {
|
for _, h := range c.rcvHeld {
|
||||||
h.data.Release()
|
h.data.Release()
|
||||||
}
|
}
|
||||||
@@ -1137,9 +879,9 @@ func (c *tcpConn) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
|||||||
c.writing = len(mb)
|
c.writing = len(mb)
|
||||||
// a segment is one part of a Buffer, or as much as there is to send if the tun splits it by itself
|
// a segment is one part of a Buffer, or as much as there is to send if the tun splits it by itself
|
||||||
var room [tcpParts][]byte
|
var room [tcpParts][]byte
|
||||||
parts := room[:0:1]
|
limit, parts := c.mss, room[:0:1]
|
||||||
if c.stack.segmenter != nil {
|
if c.stack.segmenter != nil {
|
||||||
parts = room[:0]
|
limit, parts = tcpSegment, room[:0]
|
||||||
}
|
}
|
||||||
for end := c.sndNxt + size; c.sndNxt != end; {
|
for end := c.sndNxt + size; c.sndNxt != end; {
|
||||||
// not a part of a segment when all of it can go later, with what is acknowledged by then
|
// not a part of a segment when all of it can go later, with what is acknowledged by then
|
||||||
@@ -1155,11 +897,7 @@ func (c *tcpConn) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
|||||||
if c.err != nil {
|
if c.err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
// the mss may have become smaller while the last segment was written
|
most := min(limit, int(c.space()))
|
||||||
most := min(c.mss, int(c.space()))
|
|
||||||
if c.stack.segmenter != nil {
|
|
||||||
most = min(tcpSegment, int(c.space()))
|
|
||||||
}
|
|
||||||
if most > c.mss {
|
if most > c.mss {
|
||||||
// the same for what the tun splits
|
// the same for what the tun splits
|
||||||
most -= most % c.mss
|
most -= most % c.mss
|
||||||
@@ -1169,7 +907,7 @@ func (c *tcpConn) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
|||||||
if c.sndNxt+uint32(n) == end {
|
if c.sndNxt+uint32(n) == end {
|
||||||
flags |= tcpPsh
|
flags |= tcpPsh
|
||||||
}
|
}
|
||||||
var b [ipv6HeaderSize + tcpHeaderSize + tcpStamps]byte
|
var b [ipv6HeaderSize + tcpHeaderSize]byte
|
||||||
h := b[:c.header(b[:], c.sndNxt, flags, nil, n)]
|
h := b[:c.header(b[:], c.sndNxt, flags, nil, n)]
|
||||||
if c.sndUna == c.sndNxt {
|
if c.sndUna == c.sndNxt {
|
||||||
// the ticks without an acknowledgment are counted from the first segment that waits for one
|
// the ticks without an acknowledgment are counted from the first segment that waits for one
|
||||||
|
|||||||
+2
-5
@@ -16,13 +16,10 @@ type Tun interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// segmenter is a tun whose other side takes TCP segments that are larger than the MTU,
|
// segmenter is a tun whose other side takes TCP segments that are larger than the MTU,
|
||||||
// splits them only if it has to send them on, and completes their checksum then.
|
// splits them only if it has to send them on, and completes their checksum then
|
||||||
// What it sends comes as it is too, not split to the MTU
|
|
||||||
type segmenter interface {
|
type segmenter interface {
|
||||||
// Segments reports whether ReadParts and WriteSegment can be used
|
// Segments reports whether WriteSegment can be used
|
||||||
Segments() bool
|
Segments() bool
|
||||||
// ReadParts waits for a packet, reads it into parts, one after the other, and returns its size
|
|
||||||
ReadParts(parts [][]byte) (int, error)
|
|
||||||
// WriteSegment writes a packet with a TCP segment whose payload is in parts, to be sent on with mss bytes
|
// WriteSegment writes a packet with a TCP segment whose payload is in parts, to be sent on with mss bytes
|
||||||
// of it at most in one. Its checksum is that of the pseudo-header only, not inverted
|
// of it at most in one. Its checksum is that of the pseudo-header only, not inverted
|
||||||
WriteSegment(header []byte, payload [][]byte, mss int) error
|
WriteSegment(header []byte, payload [][]byte, mss int) error
|
||||||
|
|||||||
+1
-43
@@ -6,8 +6,6 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
@@ -24,14 +22,6 @@ type fdTun struct {
|
|||||||
// vnet is the size of the virtio_net_hdr in front of every packet, zero if the tun has none.
|
// vnet is the size of the virtio_net_hdr in front of every packet, zero if the tun has none.
|
||||||
// With it the system does not split what it sends to the tun and takes what is not split from it
|
// With it the system does not split what it sends to the tun and takes what is not split from it
|
||||||
vnet int
|
vnet int
|
||||||
// raw is file for ReadParts, readv what it calls when there may be a packet, iovecs where that reads to:
|
|
||||||
// header, which takes the virtio_net_hdr, and the parts. read and readErr are what came of it
|
|
||||||
raw syscall.RawConn
|
|
||||||
readv func(fd uintptr) bool
|
|
||||||
header [packetRoom]byte
|
|
||||||
iovecs []unix.Iovec
|
|
||||||
read int
|
|
||||||
readErr error
|
|
||||||
|
|
||||||
writeLock sync.RWMutex
|
writeLock sync.RWMutex
|
||||||
closed bool
|
closed bool
|
||||||
@@ -39,25 +29,7 @@ type fdTun struct {
|
|||||||
|
|
||||||
// newFdTun takes a file descriptor that does not block
|
// newFdTun takes a file descriptor that does not block
|
||||||
func newFdTun(fd, vnet int) *fdTun {
|
func newFdTun(fd, vnet int) *fdTun {
|
||||||
t := &fdTun{fd: fd, file: os.NewFile(uintptr(fd), "/dev/net/tun"), vnet: vnet}
|
return &fdTun{fd: fd, file: os.NewFile(uintptr(fd), "/dev/net/tun"), vnet: vnet}
|
||||||
t.raw, _ = t.file.SyscallConn()
|
|
||||||
t.readv = func(fd uintptr) bool {
|
|
||||||
for {
|
|
||||||
n, _, errno := unix.Syscall(unix.SYS_READV, fd, uintptr(unsafe.Pointer(&t.iovecs[0])), uintptr(len(t.iovecs)))
|
|
||||||
switch errno {
|
|
||||||
case 0:
|
|
||||||
t.read, t.readErr = int(n), nil
|
|
||||||
case unix.EINTR:
|
|
||||||
continue
|
|
||||||
case unix.EAGAIN:
|
|
||||||
return false
|
|
||||||
default:
|
|
||||||
t.read, t.readErr = 0, errno
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return t
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *fdTun) ReadPacket(b []byte) ([]byte, error) {
|
func (t *fdTun) ReadPacket(b []byte) ([]byte, error) {
|
||||||
@@ -66,20 +38,6 @@ func (t *fdTun) ReadPacket(b []byte) ([]byte, error) {
|
|||||||
return b[min(t.vnet, n):n], err
|
return b[min(t.vnet, n):n], err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadParts implements segmenter
|
|
||||||
func (t *fdTun) ReadParts(parts [][]byte) (int, error) {
|
|
||||||
t.iovecs = append(t.iovecs[:0], unix.Iovec{Base: &t.header[0]})
|
|
||||||
t.iovecs[0].SetLen(t.vnet)
|
|
||||||
for i, p := range parts {
|
|
||||||
t.iovecs = append(t.iovecs, unix.Iovec{Base: &p[0]})
|
|
||||||
t.iovecs[i+1].SetLen(len(p))
|
|
||||||
}
|
|
||||||
if err := t.raw.Read(t.readv); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return max(t.read-t.vnet, 0), t.readErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *fdTun) WritePacket(header, payload []byte) error {
|
func (t *fdTun) WritePacket(header, payload []byte) error {
|
||||||
// a virtio_net_hdr of zeros leaves nothing to the system
|
// a virtio_net_hdr of zeros leaves nothing to the system
|
||||||
var vnet [packetRoom]byte
|
var vnet [packetRoom]byte
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
//go:build linux
|
|
||||||
|
|
||||||
package tun
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/rand"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A tun with a virtio_net_hdr reads one packet at a time into parts, without the header, waits for the next one,
|
|
||||||
// and stops waiting when it is closed.
|
|
||||||
func TestReadParts(t *testing.T) {
|
|
||||||
// what a tun is like: one packet for every read, and a file descriptor that does not block
|
|
||||||
fds, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_SEQPACKET|unix.SOCK_NONBLOCK|unix.SOCK_CLOEXEC, 0)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer unix.Close(fds[1])
|
|
||||||
tun := newFdTun(fds[0], vnetSize)
|
|
||||||
defer tun.closeFile()
|
|
||||||
|
|
||||||
large := make([]byte, 20000)
|
|
||||||
rand.Read(large)
|
|
||||||
parts := [][]byte{make([]byte, 8192), make([]byte, 8192), make([]byte, 8192)}
|
|
||||||
send := func(packet []byte) {
|
|
||||||
if _, err := unix.Write(fds[1], append(bytes.Repeat([]byte{0xee}, vnetSize), packet...)); err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
send(large)
|
|
||||||
send([]byte("small"))
|
|
||||||
for _, packet := range [][]byte{large, []byte("small")} {
|
|
||||||
n, err := tun.ReadParts(parts)
|
|
||||||
if err != nil || n != len(packet) || !bytes.Equal(bytes.Join(parts, nil)[:n], packet) {
|
|
||||||
t.Fatal("read ", n, " bytes of ", len(packet), ", error: ", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
time.Sleep(50 * time.Millisecond)
|
|
||||||
send([]byte("later"))
|
|
||||||
}()
|
|
||||||
if n, err := tun.ReadParts(parts); err != nil || string(parts[0][:n]) != "later" {
|
|
||||||
t.Fatal("read ", parts[0][:n], " after waiting, error: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
read := make(chan error)
|
|
||||||
go func() {
|
|
||||||
_, err := tun.ReadParts(parts)
|
|
||||||
read <- err
|
|
||||||
}()
|
|
||||||
time.Sleep(50 * time.Millisecond)
|
|
||||||
tun.closeFile()
|
|
||||||
select {
|
|
||||||
case err := <-read:
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("read after the tun was closed")
|
|
||||||
}
|
|
||||||
case <-time.After(5 * time.Second):
|
|
||||||
t.Fatal("still waits after the tun was closed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+14
-74
@@ -39,7 +39,9 @@ func (u *udpConnectionHandler) HandlePacket(src net.Destination, dst net.Destina
|
|||||||
u.RLock()
|
u.RLock()
|
||||||
conn, found := u.udpConns[src]
|
conn, found := u.udpConns[src]
|
||||||
if found {
|
if found {
|
||||||
if !conn.queue.push(data) {
|
select {
|
||||||
|
case conn.egress <- data:
|
||||||
|
default:
|
||||||
errors.LogDebug(context.Background(), "drop udp with size ", data.Len(), " to ", dst.NetAddr(), " original ", conn.dst.NetAddr(), " > queue full")
|
errors.LogDebug(context.Background(), "drop udp with size ", data.Len(), " to ", dst.NetAddr(), " original ", conn.dst.NetAddr(), " > queue full")
|
||||||
data.Release()
|
data.Release()
|
||||||
}
|
}
|
||||||
@@ -53,14 +55,17 @@ func (u *udpConnectionHandler) HandlePacket(src net.Destination, dst net.Destina
|
|||||||
|
|
||||||
conn, found = u.udpConns[src]
|
conn, found = u.udpConns[src]
|
||||||
if !found {
|
if !found {
|
||||||
conn = &udpConn{handler: u, queue: newPacketQueue(udpQueueLimit), src: src, dst: dst}
|
egress := make(chan *buf.Buffer, 1024)
|
||||||
|
conn = &udpConn{handler: u, egress: egress, src: src, dst: dst}
|
||||||
u.udpConns[src] = conn
|
u.udpConns[src] = conn
|
||||||
|
|
||||||
go u.handleConnection(conn, dst)
|
go u.handleConnection(conn, dst)
|
||||||
}
|
}
|
||||||
|
|
||||||
// queue the packet for the reader of the connection, or discard it
|
// send packet data to the egress channel, if it has buffer, or discard
|
||||||
if !conn.queue.push(data) {
|
select {
|
||||||
|
case conn.egress <- data:
|
||||||
|
default:
|
||||||
errors.LogDebug(context.Background(), "drop udp with size ", data.Len(), " to ", dst.NetAddr(), " original ", conn.dst.NetAddr(), " > queue full 2")
|
errors.LogDebug(context.Background(), "drop udp with size ", data.Len(), " to ", dst.NetAddr(), " original ", conn.dst.NetAddr(), " > queue full 2")
|
||||||
data.Release()
|
data.Release()
|
||||||
}
|
}
|
||||||
@@ -71,7 +76,7 @@ func (u *udpConnectionHandler) connectionFinished(conn *udpConn) {
|
|||||||
// Close runs twice per flow; a newer conn may already own this src.
|
// Close runs twice per flow; a newer conn may already own this src.
|
||||||
if u.udpConns[conn.src] == conn {
|
if u.udpConns[conn.src] == conn {
|
||||||
delete(u.udpConns, conn.src)
|
delete(u.udpConns, conn.src)
|
||||||
conn.queue.close()
|
close(conn.egress)
|
||||||
}
|
}
|
||||||
u.Unlock()
|
u.Unlock()
|
||||||
}
|
}
|
||||||
@@ -80,13 +85,13 @@ func (u *udpConnectionHandler) connectionFinished(conn *udpConn) {
|
|||||||
type udpConn struct {
|
type udpConn struct {
|
||||||
handler *udpConnectionHandler
|
handler *udpConnectionHandler
|
||||||
|
|
||||||
queue *packetQueue
|
egress chan *buf.Buffer
|
||||||
src net.Destination
|
src net.Destination
|
||||||
dst net.Destination
|
dst net.Destination
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *udpConn) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
func (c *udpConn) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
||||||
b, ok := c.queue.pop()
|
b, ok := <-c.egress
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, io.EOF
|
return nil, io.EOF
|
||||||
}
|
}
|
||||||
@@ -95,7 +100,7 @@ func (c *udpConn) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
|||||||
|
|
||||||
// Read packets from the connection
|
// Read packets from the connection
|
||||||
func (c *udpConn) Read(p []byte) (int, error) {
|
func (c *udpConn) Read(p []byte) (int, error) {
|
||||||
b, ok := c.queue.pop()
|
b, ok := <-c.egress
|
||||||
if !ok {
|
if !ok {
|
||||||
return 0, io.EOF
|
return 0, io.EOF
|
||||||
}
|
}
|
||||||
@@ -132,7 +137,7 @@ func (c *udpConn) Write(p []byte) (int, error) {
|
|||||||
// sending packets back mean sending payload with source/destination reversed
|
// sending packets back mean sending payload with source/destination reversed
|
||||||
err := c.handler.writePacket(p, c.dst, c.src)
|
err := c.handler.writePacket(p, c.dst, c.src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return len(p), nil
|
return len(p), nil
|
||||||
@@ -163,68 +168,3 @@ func (c *udpConn) SetReadDeadline(t time.Time) error {
|
|||||||
func (c *udpConn) SetWriteDeadline(t time.Time) error {
|
func (c *udpConn) SetWriteDeadline(t time.Time) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// udpQueueLimit bounds the packets waiting for one UDP flow; more are dropped.
|
|
||||||
const udpQueueLimit = 1024
|
|
||||||
|
|
||||||
// packetQueue holds the packets waiting for one UDP flow. Unlike a buffered
|
|
||||||
// channel of the same bound it only allocates for packets actually queued, so
|
|
||||||
// the many idle flows kept until the idle timeout cost next to nothing.
|
|
||||||
type packetQueue struct {
|
|
||||||
mu sync.Mutex
|
|
||||||
items []*buf.Buffer
|
|
||||||
limit int
|
|
||||||
notify chan struct{}
|
|
||||||
closed bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func newPacketQueue(limit int) *packetQueue {
|
|
||||||
return &packetQueue{limit: limit, notify: make(chan struct{}, 1)}
|
|
||||||
}
|
|
||||||
|
|
||||||
// push queues b and reports whether it was accepted.
|
|
||||||
func (q *packetQueue) push(b *buf.Buffer) bool {
|
|
||||||
q.mu.Lock()
|
|
||||||
defer q.mu.Unlock()
|
|
||||||
if q.closed || len(q.items) >= q.limit {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
q.items = append(q.items, b)
|
|
||||||
select {
|
|
||||||
case q.notify <- struct{}{}:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// pop blocks until a packet is queued or the queue is closed and drained.
|
|
||||||
func (q *packetQueue) pop() (*buf.Buffer, bool) {
|
|
||||||
for {
|
|
||||||
q.mu.Lock()
|
|
||||||
if len(q.items) > 0 {
|
|
||||||
b := q.items[0]
|
|
||||||
q.items[0] = nil
|
|
||||||
q.items = q.items[1:]
|
|
||||||
if len(q.items) == 0 {
|
|
||||||
q.items = nil
|
|
||||||
}
|
|
||||||
q.mu.Unlock()
|
|
||||||
return b, true
|
|
||||||
}
|
|
||||||
if q.closed {
|
|
||||||
q.mu.Unlock()
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
q.mu.Unlock()
|
|
||||||
<-q.notify
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *packetQueue) close() {
|
|
||||||
q.mu.Lock()
|
|
||||||
defer q.mu.Unlock()
|
|
||||||
if !q.closed {
|
|
||||||
q.closed = true
|
|
||||||
close(q.notify)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -6,9 +6,11 @@ import (
|
|||||||
gotls "crypto/tls"
|
gotls "crypto/tls"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"io"
|
"io"
|
||||||
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/app/dispatcher"
|
"github.com/xtls/xray-core/app/dispatcher"
|
||||||
"github.com/xtls/xray-core/app/reverse"
|
"github.com/xtls/xray-core/app/reverse"
|
||||||
@@ -546,7 +548,7 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
|
|||||||
}
|
}
|
||||||
|
|
||||||
var input *bytes.Reader
|
var input *bytes.Reader
|
||||||
var rawInput **bytes.Buffer
|
var rawInput *bytes.Buffer
|
||||||
switch requestAddons.Flow {
|
switch requestAddons.Flow {
|
||||||
case vless.XRV:
|
case vless.XRV:
|
||||||
if account.Flow == requestAddons.Flow {
|
if account.Flow == requestAddons.Flow {
|
||||||
@@ -558,23 +560,30 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
|
|||||||
inbound.CanSpliceCopy = 3
|
inbound.CanSpliceCopy = 3
|
||||||
fallthrough // we will break Mux connections that contain TCP requests
|
fallthrough // we will break Mux connections that contain TCP requests
|
||||||
case protocol.RequestCommandTCP:
|
case protocol.RequestCommandTCP:
|
||||||
var target net.Conn
|
var t reflect.Type
|
||||||
|
var p uintptr
|
||||||
if commonConn, ok := connection.(*encryption.CommonConn); ok {
|
if commonConn, ok := connection.(*encryption.CommonConn); ok {
|
||||||
if _, ok := commonConn.Conn.(*encryption.XorConn); ok || !proxy.IsRAWTransportWithoutSecurity(iConn) {
|
if _, ok := commonConn.Conn.(*encryption.XorConn); ok || !proxy.IsRAWTransportWithoutSecurity(iConn) {
|
||||||
inbound.CanSpliceCopy = 3 // full-random xorConn / non-RAW transport / another securityConn should not be penetrated
|
inbound.CanSpliceCopy = 3 // full-random xorConn / non-RAW transport / another securityConn should not be penetrated
|
||||||
}
|
}
|
||||||
target = commonConn
|
t = reflect.TypeOf(commonConn).Elem()
|
||||||
|
p = uintptr(unsafe.Pointer(commonConn))
|
||||||
} else if tlsConn, ok := iConn.(*tls.Conn); ok {
|
} else if tlsConn, ok := iConn.(*tls.Conn); ok {
|
||||||
if tlsConn.ConnectionState().Version != gotls.VersionTLS13 {
|
if tlsConn.ConnectionState().Version != gotls.VersionTLS13 {
|
||||||
return errors.New(`failed to use `+requestAddons.Flow+`, found outer tls version `, tlsConn.ConnectionState().Version)
|
return errors.New(`failed to use `+requestAddons.Flow+`, found outer tls version `, tlsConn.ConnectionState().Version)
|
||||||
}
|
}
|
||||||
target = tlsConn.Conn
|
t = reflect.TypeOf(tlsConn.Conn).Elem()
|
||||||
|
p = uintptr(unsafe.Pointer(tlsConn.Conn))
|
||||||
} else if realityConn, ok := iConn.(*reality.Conn); ok {
|
} else if realityConn, ok := iConn.(*reality.Conn); ok {
|
||||||
target = realityConn.Conn
|
t = reflect.TypeOf(realityConn.Conn).Elem()
|
||||||
|
p = uintptr(unsafe.Pointer(realityConn.Conn))
|
||||||
} else {
|
} else {
|
||||||
return errors.New("XTLS only supports TLS and REALITY directly for now.")
|
return errors.New("XTLS only supports TLS and REALITY directly for now.")
|
||||||
}
|
}
|
||||||
input, rawInput = vless.AccessTLSBuffers(target)
|
i, _ := t.FieldByName("input")
|
||||||
|
r, _ := t.FieldByName("rawInput")
|
||||||
|
input = (*bytes.Reader)(unsafe.Pointer(p + i.Offset))
|
||||||
|
rawInput = (*bytes.Buffer)(unsafe.Pointer(p + r.Offset))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return errors.New("account " + account.ID.String() + " is not able to use the flow " + requestAddons.Flow)
|
return errors.New("account " + account.ID.String() + " is not able to use the flow " + requestAddons.Flow)
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
gotls "crypto/tls"
|
gotls "crypto/tls"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
utls "github.com/refraction-networking/utls"
|
utls "github.com/refraction-networking/utls"
|
||||||
proxymanConfig "github.com/xtls/xray-core/app/proxyman"
|
proxymanConfig "github.com/xtls/xray-core/app/proxyman"
|
||||||
@@ -242,7 +244,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
|||||||
}
|
}
|
||||||
|
|
||||||
var input *bytes.Reader
|
var input *bytes.Reader
|
||||||
var rawInput **bytes.Buffer
|
var rawInput *bytes.Buffer
|
||||||
allowUDP443 := false
|
allowUDP443 := false
|
||||||
switch requestAddons.Flow {
|
switch requestAddons.Flow {
|
||||||
case vless.XRV + "-udp443":
|
case vless.XRV + "-udp443":
|
||||||
@@ -259,22 +261,30 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
|||||||
case protocol.RequestCommandMux:
|
case protocol.RequestCommandMux:
|
||||||
fallthrough // let server break Mux connections that contain TCP requests
|
fallthrough // let server break Mux connections that contain TCP requests
|
||||||
case protocol.RequestCommandTCP, protocol.RequestCommandRvs:
|
case protocol.RequestCommandTCP, protocol.RequestCommandRvs:
|
||||||
var target net.Conn
|
var t reflect.Type
|
||||||
|
var p uintptr
|
||||||
if commonConn, ok := conn.(*encryption.CommonConn); ok {
|
if commonConn, ok := conn.(*encryption.CommonConn); ok {
|
||||||
if _, ok := commonConn.Conn.(*encryption.XorConn); ok || !proxy.IsRAWTransportWithoutSecurity(iConn) {
|
if _, ok := commonConn.Conn.(*encryption.XorConn); ok || !proxy.IsRAWTransportWithoutSecurity(iConn) {
|
||||||
ob.CanSpliceCopy = 3 // full-random xorConn / non-RAW transport / another securityConn should not be penetrated
|
ob.CanSpliceCopy = 3 // full-random xorConn / non-RAW transport / another securityConn should not be penetrated
|
||||||
}
|
}
|
||||||
target = commonConn
|
t = reflect.TypeOf(commonConn).Elem()
|
||||||
|
p = uintptr(unsafe.Pointer(commonConn))
|
||||||
} else if tlsConn, ok := iConn.(*tls.Conn); ok {
|
} else if tlsConn, ok := iConn.(*tls.Conn); ok {
|
||||||
target = tlsConn.Conn
|
t = reflect.TypeOf(tlsConn.Conn).Elem()
|
||||||
|
p = uintptr(unsafe.Pointer(tlsConn.Conn))
|
||||||
} else if utlsConn, ok := iConn.(*tls.UConn); ok {
|
} else if utlsConn, ok := iConn.(*tls.UConn); ok {
|
||||||
target = utlsConn.Conn
|
t = reflect.TypeOf(utlsConn.Conn).Elem()
|
||||||
|
p = uintptr(unsafe.Pointer(utlsConn.Conn))
|
||||||
} else if realityConn, ok := iConn.(*reality.UConn); ok {
|
} else if realityConn, ok := iConn.(*reality.UConn); ok {
|
||||||
target = realityConn.Conn
|
t = reflect.TypeOf(realityConn.Conn).Elem()
|
||||||
|
p = uintptr(unsafe.Pointer(realityConn.Conn))
|
||||||
} else {
|
} else {
|
||||||
return errors.New("XTLS only supports TLS and REALITY directly for now.")
|
return errors.New("XTLS only supports TLS and REALITY directly for now.")
|
||||||
}
|
}
|
||||||
input, rawInput = vless.AccessTLSBuffers(target)
|
i, _ := t.FieldByName("input")
|
||||||
|
r, _ := t.FieldByName("rawInput")
|
||||||
|
input = (*bytes.Reader)(unsafe.Pointer(p + i.Offset))
|
||||||
|
rawInput = (*bytes.Buffer)(unsafe.Pointer(p + r.Offset))
|
||||||
default:
|
default:
|
||||||
panic("unknown VLESS request command")
|
panic("unknown VLESS request command")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,28 +5,7 @@
|
|||||||
// clients with 'socks' for proxying.
|
// clients with 'socks' for proxying.
|
||||||
package vless
|
package vless
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"net"
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
None = "none"
|
None = "none"
|
||||||
XRV = "xtls-rprx-vision"
|
XRV = "xtls-rprx-vision"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Old TLS lib holds bytes.Buffer, but new TLS lib holds *bytes.Buffer
|
|
||||||
// Since Xray has 3 TLS libraries (std, utls, REALITY server)
|
|
||||||
// we detect the type of rawInput and return the appropriate pointer
|
|
||||||
func AccessTLSBuffers(conn net.Conn) (*bytes.Reader, **bytes.Buffer) {
|
|
||||||
input := utils.AccessField[bytes.Reader](conn, "input")
|
|
||||||
field := reflect.ValueOf(conn).Elem().FieldByName("rawInput")
|
|
||||||
if field.Kind() == reflect.Pointer {
|
|
||||||
return input, utils.AccessField[*bytes.Buffer](conn, "rawInput")
|
|
||||||
}
|
|
||||||
raw := utils.AccessField[bytes.Buffer](conn, "rawInput")
|
|
||||||
return input, &raw
|
|
||||||
}
|
|
||||||
|
|||||||
+14
-11
@@ -16,9 +16,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type bind struct {
|
type bind struct {
|
||||||
ctx context.Context
|
|
||||||
resolveFunc func(host string) (net.IP, error)
|
resolveFunc func(host string) (net.IP, error)
|
||||||
listenFunc func(ctx context.Context) (net.PacketConn, error)
|
listenFunc func() (net.PacketConn, error)
|
||||||
downFunc func() error
|
downFunc func() error
|
||||||
reserved []byte
|
reserved []byte
|
||||||
|
|
||||||
@@ -27,12 +26,6 @@ type bind struct {
|
|||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *bind) SetCtx(ctx context.Context) {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
b.ctx = ctx
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *bind) Open(port uint16) (fns []conn.ReceiveFunc, actualPort uint16, err error) {
|
func (b *bind) Open(port uint16) (fns []conn.ReceiveFunc, actualPort uint16, err error) {
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
defer b.mu.Unlock()
|
defer b.mu.Unlock()
|
||||||
@@ -41,7 +34,7 @@ func (b *bind) Open(port uint16) (fns []conn.ReceiveFunc, actualPort uint16, err
|
|||||||
return nil, 0, conn.ErrBindAlreadyOpen
|
return nil, 0, conn.ErrBindAlreadyOpen
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err := b.listenFunc(b.ctx)
|
c, err := b.listenFunc()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
@@ -59,9 +52,12 @@ func (b *bind) Open(port uint16) (fns []conn.ReceiveFunc, actualPort uint16, err
|
|||||||
case <-ch:
|
case <-ch:
|
||||||
default:
|
default:
|
||||||
errors.LogErrorInner(context.Background(), err, "unexpected closed")
|
errors.LogErrorInner(context.Background(), err, "unexpected closed")
|
||||||
if b.downFunc != nil {
|
b.mu.Lock()
|
||||||
|
downFunc := b.downFunc
|
||||||
|
b.mu.Unlock()
|
||||||
|
if downFunc != nil {
|
||||||
go func() {
|
go func() {
|
||||||
common.Must(b.downFunc())
|
common.Must(downFunc())
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -83,6 +79,13 @@ func (b *bind) Open(port uint16) (fns []conn.ReceiveFunc, actualPort uint16, err
|
|||||||
}, uint16(c.LocalAddr().(*net.UDPAddr).Port), nil
|
}, uint16(c.LocalAddr().(*net.UDPAddr).Port), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// setDownFunc sets downFunc after the device is created, since the device may already be using the bind.
|
||||||
|
func (b *bind) setDownFunc(f func() error) {
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
b.downFunc = f
|
||||||
|
}
|
||||||
|
|
||||||
func (b *bind) Close() error {
|
func (b *bind) Close() error {
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
defer b.mu.Unlock()
|
defer b.mu.Unlock()
|
||||||
|
|||||||
+116
-132
@@ -7,7 +7,6 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.zx2c4.com/wireguard/tun"
|
"golang.zx2c4.com/wireguard/tun"
|
||||||
@@ -32,13 +31,17 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
p policy.Manager
|
conf *DeviceConfig
|
||||||
|
policyManager policy.Manager
|
||||||
|
dns dns.Client
|
||||||
|
|
||||||
canup *atomic.Bool
|
streamSettings *internet.MemoryStreamConfig
|
||||||
|
uplinkCounter stats.Counter
|
||||||
|
downlinkCounter stats.Counter
|
||||||
|
|
||||||
|
tun tun.Device
|
||||||
tnet *Net
|
tnet *Net
|
||||||
bind *bind
|
|
||||||
dev *device.Device
|
dev *device.Device
|
||||||
ipc string
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,93 +117,24 @@ func NewClient(ctx context.Context, conf *DeviceConfig) (*Handler, error) {
|
|||||||
errors.LogWarning(context.Background(), "Using kernel TUN")
|
errors.LogWarning(context.Background(), "Using kernel TUN")
|
||||||
tun, tnet, err = createKernelTun(localAddresses, dnses, int(conf.Mtu))
|
tun, tnet, err = createKernelTun(localAddresses, dnses, int(conf.Mtu))
|
||||||
} else {
|
} else {
|
||||||
errors.LogWarning(context.Background(), "Using built-in TCP/IP stack")
|
errors.LogWarning(context.Background(), "Using gVisor TUN")
|
||||||
tun, tnet, err = CreateNetTUN(localAddresses, dnses, int(conf.Mtu), nil)
|
tun, tnet, _, err = CreateNetTUN(localAddresses, dnses, int(conf.Mtu), true)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
blockup := newblockup(tun)
|
|
||||||
bind := &bind{}
|
|
||||||
logger := &device.Logger{
|
|
||||||
Verbosef: func(format string, args ...any) {
|
|
||||||
log.Record(&log.GeneralMessage{
|
|
||||||
Severity: log.Severity_Debug,
|
|
||||||
Content: fmt.Sprintf(format, args...),
|
|
||||||
})
|
|
||||||
},
|
|
||||||
Errorf: func(format string, args ...any) {
|
|
||||||
log.Record(&log.GeneralMessage{
|
|
||||||
Severity: log.Severity_Error,
|
|
||||||
Content: fmt.Sprintf(format, args...),
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}
|
|
||||||
dev := device.NewDevice(blockup, bind, logger)
|
|
||||||
bind.ctx = context.Background()
|
|
||||||
bind.resolveFunc = MakeResolveLocal(d, streamSettings)
|
|
||||||
bind.listenFunc = func(ctx context.Context) (net.PacketConn, error) {
|
|
||||||
dest, err := net.ParseDestination("udp:" + conf.Peers[0].Endpoint)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var pktConn net.PacketConn
|
|
||||||
if streamSettings.FinalMask != nil {
|
|
||||||
conn, err := streamSettings.FinalMask.DialUDP(ctx, dest)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("failed to dial to dest").Base(err)
|
|
||||||
}
|
|
||||||
pktConn = conn.(*net.PacketConnWrapper).PacketConn
|
|
||||||
} else {
|
|
||||||
conn, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("failed to dial to dest").Base(err)
|
|
||||||
}
|
|
||||||
switch c := conn.(type) {
|
|
||||||
case *net.PacketConnWrapper:
|
|
||||||
pktConn = c.PacketConn
|
|
||||||
case *cnc.Connection:
|
|
||||||
pktConn = &internet.FakePacketConn{Conn: c}
|
|
||||||
default:
|
|
||||||
panic(reflect.TypeOf(c))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if uplinkCounter != nil || downlinkCounter != nil {
|
|
||||||
pktConn = &PacketCounterConnection{
|
|
||||||
PacketConn: pktConn,
|
|
||||||
ReadCounter: downlinkCounter,
|
|
||||||
WriteCounter: uplinkCounter,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return pktConn, nil
|
|
||||||
}
|
|
||||||
bind.downFunc = dev.Down
|
|
||||||
bind.reserved = conf.Reserved
|
|
||||||
var cfg strings.Builder
|
|
||||||
cfg.WriteString("private_key=" + conf.SecretKey + "\n")
|
|
||||||
for _, peer := range conf.Peers {
|
|
||||||
cfg.WriteString("public_key=" + peer.PublicKey + "\n")
|
|
||||||
if peer.PreSharedKey != "" {
|
|
||||||
cfg.WriteString("preshared_key=" + peer.PreSharedKey + "\n")
|
|
||||||
}
|
|
||||||
cfg.WriteString("endpoint=" + peer.Endpoint + "\n")
|
|
||||||
for _, ip := range peer.AllowedIps {
|
|
||||||
cfg.WriteString("allowed_ip=" + ip + "\n")
|
|
||||||
}
|
|
||||||
if peer.KeepAlive != "" {
|
|
||||||
cfg.WriteString("persistent_keepalive_interval=" + peer.KeepAlive + "\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Handler{
|
return &Handler{
|
||||||
p: p,
|
conf: conf,
|
||||||
|
policyManager: p,
|
||||||
|
dns: d,
|
||||||
|
|
||||||
canup: blockup.canup,
|
streamSettings: streamSettings,
|
||||||
|
uplinkCounter: uplinkCounter,
|
||||||
|
downlinkCounter: downlinkCounter,
|
||||||
|
|
||||||
|
tun: tun,
|
||||||
tnet: tnet,
|
tnet: tnet,
|
||||||
bind: bind,
|
|
||||||
dev: dev,
|
|
||||||
ipc: cfg.String(),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,7 +149,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
|||||||
ob.CanSpliceCopy = 3
|
ob.CanSpliceCopy = 3
|
||||||
dialer.SetOutboundGateway(ctx, ob)
|
dialer.SetOutboundGateway(ctx, ob)
|
||||||
|
|
||||||
if err := h.up(ctx); err != nil {
|
if err := h.init(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,7 +159,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
|||||||
newCtx, newCancel = context.WithCancel(context.Background())
|
newCtx, newCancel = context.WithCancel(context.Background())
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionPolicy := h.p.ForLevel(0)
|
sessionPolicy := h.policyManager.ForLevel(0)
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
timer := signal.CancelAfterInactivity(ctx, func() {
|
timer := signal.CancelAfterInactivity(ctx, func() {
|
||||||
cancel()
|
cancel()
|
||||||
@@ -300,40 +234,119 @@ func (h *Handler) Close() (err error) {
|
|||||||
if h.dev != nil {
|
if h.dev != nil {
|
||||||
h.dev.Close()
|
h.dev.Close()
|
||||||
h.dev = nil
|
h.dev = nil
|
||||||
|
h.tun = nil
|
||||||
|
} else if h.tun != nil {
|
||||||
|
h.tun.Close()
|
||||||
|
h.tun = nil
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) up(ctx context.Context) error {
|
func (h *Handler) init(ctx context.Context) error {
|
||||||
h.mu.Lock()
|
h.mu.Lock()
|
||||||
defer h.mu.Unlock()
|
defer h.mu.Unlock()
|
||||||
if h.dev == nil {
|
if h.tun == nil {
|
||||||
return errors.New("closed")
|
return errors.New("closed")
|
||||||
}
|
}
|
||||||
h.bind.SetCtx(ctx)
|
if h.dev != nil {
|
||||||
if !h.canup.Load() {
|
|
||||||
h.canup.Store(true)
|
|
||||||
}
|
|
||||||
if len(h.ipc) > 0 {
|
|
||||||
if err := h.dev.IpcSet(h.ipc); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
h.ipc = ""
|
|
||||||
}
|
|
||||||
return h.dev.Up()
|
return h.dev.Up()
|
||||||
}
|
}
|
||||||
|
resolveFunc := h.resolveLocal
|
||||||
|
listenFunc := func() (net.PacketConn, error) {
|
||||||
|
dest, err := net.ParseDestination("udp:" + h.conf.Peers[0].Endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var pktConn net.PacketConn
|
||||||
|
if h.streamSettings.FinalMask != nil {
|
||||||
|
conn, err := h.streamSettings.FinalMask.DialUDP(ctx, dest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("failed to dial to dest").Base(err)
|
||||||
|
}
|
||||||
|
pktConn = conn.(*net.PacketConnWrapper).PacketConn
|
||||||
|
} else {
|
||||||
|
conn, err := internet.DialSystem(ctx, dest, h.streamSettings.SocketSettings)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("failed to dial to dest").Base(err)
|
||||||
|
}
|
||||||
|
switch c := conn.(type) {
|
||||||
|
case *net.PacketConnWrapper:
|
||||||
|
pktConn = c.PacketConn
|
||||||
|
case *cnc.Connection:
|
||||||
|
pktConn = &internet.FakePacketConn{Conn: c}
|
||||||
|
default:
|
||||||
|
panic(reflect.TypeOf(c))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if h.uplinkCounter != nil || h.downlinkCounter != nil {
|
||||||
|
pktConn = &PacketCounterConnection{
|
||||||
|
PacketConn: pktConn,
|
||||||
|
ReadCounter: h.downlinkCounter,
|
||||||
|
WriteCounter: h.uplinkCounter,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pktConn, nil
|
||||||
|
}
|
||||||
|
// device.NewDevice may use the bind right away (Up -> BindUpdate -> Open),
|
||||||
|
// so everything it reads must be set before creating the device.
|
||||||
|
bind := &bind{
|
||||||
|
resolveFunc: resolveFunc,
|
||||||
|
listenFunc: listenFunc,
|
||||||
|
reserved: h.conf.Reserved,
|
||||||
|
}
|
||||||
|
logger := &device.Logger{
|
||||||
|
Verbosef: func(format string, args ...any) {
|
||||||
|
log.Record(&log.GeneralMessage{
|
||||||
|
Severity: log.Severity_Debug,
|
||||||
|
Content: fmt.Sprintf(format, args...),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
Errorf: func(format string, args ...any) {
|
||||||
|
log.Record(&log.GeneralMessage{
|
||||||
|
Severity: log.Severity_Error,
|
||||||
|
Content: fmt.Sprintf(format, args...),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
dev := device.NewDevice(h.tun, bind, logger)
|
||||||
|
bind.setDownFunc(dev.Down)
|
||||||
|
var cfg strings.Builder
|
||||||
|
cfg.WriteString("private_key=" + h.conf.SecretKey + "\n")
|
||||||
|
for _, peer := range h.conf.Peers {
|
||||||
|
cfg.WriteString("public_key=" + peer.PublicKey + "\n")
|
||||||
|
if peer.PreSharedKey != "" {
|
||||||
|
cfg.WriteString("preshared_key=" + peer.PreSharedKey + "\n")
|
||||||
|
}
|
||||||
|
cfg.WriteString("endpoint=" + peer.Endpoint + "\n")
|
||||||
|
for _, ip := range peer.AllowedIps {
|
||||||
|
cfg.WriteString("allowed_ip=" + ip + "\n")
|
||||||
|
}
|
||||||
|
if peer.KeepAlive != "" {
|
||||||
|
cfg.WriteString("persistent_keepalive_interval=" + peer.KeepAlive + "\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err := dev.IpcSet(cfg.String())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = dev.Up()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.dev = dev
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func MakeResolveLocal(d dns.Client, streamSettings *internet.MemoryStreamConfig) func(host string) (net.IP, error) {
|
func (h *Handler) resolveLocal(host string) (net.IP, error) {
|
||||||
return func(host string) (net.IP, error) {
|
|
||||||
if ip := net.ParseIP(host); ip != nil {
|
if ip := net.ParseIP(host); ip != nil {
|
||||||
return ip, nil
|
return ip, nil
|
||||||
}
|
}
|
||||||
ips, _, err := d.LookupIP(host, dns.IPOption{IPv4Enable: true, IPv6Enable: true})
|
ips, _, err := h.dns.LookupIP(host, dns.IPOption{IPv4Enable: true, IPv6Enable: true})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
got := ips
|
got := ips
|
||||||
if streamSettings.SocketSettings != nil {
|
if h.streamSettings.SocketSettings != nil {
|
||||||
var got4, got6 []net.IP
|
var got4, got6 []net.IP
|
||||||
for _, ip := range ips {
|
for _, ip := range ips {
|
||||||
if ip.To4() != nil {
|
if ip.To4() != nil {
|
||||||
@@ -342,7 +355,7 @@ func MakeResolveLocal(d dns.Client, streamSettings *internet.MemoryStreamConfig)
|
|||||||
got6 = append(got6, ip)
|
got6 = append(got6, ip)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch streamSettings.SocketSettings.DomainStrategy {
|
switch h.streamSettings.SocketSettings.DomainStrategy {
|
||||||
case internet.DomainStrategy_AS_IS, internet.DomainStrategy_USE_IP, internet.DomainStrategy_FORCE_IP:
|
case internet.DomainStrategy_AS_IS, internet.DomainStrategy_USE_IP, internet.DomainStrategy_FORCE_IP:
|
||||||
got = ips
|
got = ips
|
||||||
case internet.DomainStrategy_USE_IP4, internet.DomainStrategy_FORCE_IP4:
|
case internet.DomainStrategy_USE_IP4, internet.DomainStrategy_FORCE_IP4:
|
||||||
@@ -366,7 +379,6 @@ func MakeResolveLocal(d dns.Client, streamSettings *internet.MemoryStreamConfig)
|
|||||||
}
|
}
|
||||||
return got[dice.Roll(len(got))], nil
|
return got[dice.Roll(len(got))], nil
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
type UDPConnClient struct {
|
type UDPConnClient struct {
|
||||||
net.PacketConn
|
net.PacketConn
|
||||||
@@ -498,31 +510,3 @@ func (c *cache) Cache(host string, saddr []string, ttl uint32) {
|
|||||||
deadline: time.Now().Add(time.Second * time.Duration(ttl)),
|
deadline: time.Now().Add(time.Second * time.Duration(ttl)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type blockup struct {
|
|
||||||
tun.Device
|
|
||||||
ch chan tun.Event
|
|
||||||
canup *atomic.Bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func newblockup(t tun.Device) *blockup {
|
|
||||||
ch := make(chan tun.Event, cap(t.Events()))
|
|
||||||
canup := &atomic.Bool{}
|
|
||||||
go func() {
|
|
||||||
for e := range t.Events() {
|
|
||||||
if e&tun.EventUp != 0 && !canup.Load() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case ch <- e:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
close(ch)
|
|
||||||
}()
|
|
||||||
return &blockup{Device: t, ch: ch, canup: canup}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *blockup) Events() <-chan tun.Event {
|
|
||||||
return b.ch
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package wireguard
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"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"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CreateICMPEchoResponder answers ICMP echo requests from peers locally, the way
|
||||||
|
// the TUN inbound does: ICMP is not proxied, but ping and connectivity checks
|
||||||
|
// through the tunnel get a reply instead of timing out.
|
||||||
|
//
|
||||||
|
// In promiscuous mode gVisor skips its own IPv4 echo reply for addresses that are
|
||||||
|
// not assigned to the NIC and leaves it to a custom handler; IPv6 is registered
|
||||||
|
// too so both families behave the same.
|
||||||
|
func CreateICMPEchoResponder(gstack *stack.Stack) {
|
||||||
|
gstack.SetTransportProtocolHandler(icmp.ProtocolNumber4, func(id stack.TransportEndpointID, pkt *stack.PacketBuffer) bool {
|
||||||
|
return handleICMPEcho(gstack, header.IPv4ProtocolNumber, id, pkt)
|
||||||
|
})
|
||||||
|
gstack.SetTransportProtocolHandler(icmp.ProtocolNumber6, func(id stack.TransportEndpointID, pkt *stack.PacketBuffer) bool {
|
||||||
|
return handleICMPEcho(gstack, header.IPv6ProtocolNumber, id, pkt)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleICMPEcho(gstack *stack.Stack, 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
|
||||||
|
}
|
||||||
|
|
||||||
|
headerBytes := pkt.TransportHeader().Slice()
|
||||||
|
payloadBytes := pkt.Data().AsRange().ToSlice()
|
||||||
|
message := make([]byte, len(headerBytes)+len(payloadBytes))
|
||||||
|
copy(message, headerBytes)
|
||||||
|
copy(message[len(headerBytes):], payloadBytes)
|
||||||
|
|
||||||
|
if _, _, ok := tunicmp.ParseEchoRequest(netProto, message); !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
reply, err := tunicmp.BuildLocalEchoReply(netProto, message, dstIP, srcIP)
|
||||||
|
if err != nil {
|
||||||
|
errors.LogInfoInner(context.Background(), err, "failed to build local icmp echo reply")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if err := writeRawICMPPacket(gstack, netProto, reply, dstIP, srcIP); err != nil {
|
||||||
|
errors.LogInfoInner(context.Background(), err, "failed to write local icmp echo reply")
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeRawICMPPacket(gstack *stack.Stack, netProto tcpip.NetworkProtocolNumber, message []byte, srcIP, dstIP tcpip.Address) error {
|
||||||
|
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||||
|
ReserveHeaderBytes: header.IPv6MinimumSize,
|
||||||
|
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(header.ICMPv4ProtocolNumber),
|
||||||
|
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: header.ICMPv6ProtocolNumber,
|
||||||
|
HopLimit: 64,
|
||||||
|
SrcAddr: srcIP,
|
||||||
|
DstAddr: dstIP,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := gstack.WriteRawPacket(1, netProto, buffer.MakeWithView(pkt.ToView())); err != nil {
|
||||||
|
return errors.New("failed to write raw icmp packet back to stack ", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
package wireguard
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"net/netip"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/checksum"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newICMPTestStack(t *testing.T) *netTun {
|
||||||
|
t.Helper()
|
||||||
|
dev, _, gstack, err := CreateNetTUN([]netip.Addr{
|
||||||
|
netip.MustParseAddr("10.66.0.1"),
|
||||||
|
netip.MustParseAddr("fd00::1"),
|
||||||
|
}, nil, 1420, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Cleanup(func() { dev.Close() })
|
||||||
|
CreateForwarder(gstack, func(conn net.Conn, dest net.Destination) { conn.Close() })
|
||||||
|
CreateICMPEchoResponder(gstack)
|
||||||
|
return dev.(*netTun)
|
||||||
|
}
|
||||||
|
|
||||||
|
// startReader must run before the request is written: the stack may answer
|
||||||
|
// synchronously inside Write, and netTun hands packets over an unbuffered channel.
|
||||||
|
func startReader(dev *netTun) <-chan []byte {
|
||||||
|
got := make(chan []byte, 1)
|
||||||
|
go func() {
|
||||||
|
buf := make([]byte, 2048)
|
||||||
|
sizes := make([]int, 1)
|
||||||
|
if _, err := dev.Read([][]byte{buf}, sizes, 0); err == nil {
|
||||||
|
got <- buf[:sizes[0]]
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return got
|
||||||
|
}
|
||||||
|
|
||||||
|
func awaitPacket(t *testing.T, got <-chan []byte) []byte {
|
||||||
|
t.Helper()
|
||||||
|
select {
|
||||||
|
case p := <-got:
|
||||||
|
return p
|
||||||
|
case <-time.After(2 * time.Second):
|
||||||
|
t.Fatal("no echo reply from the stack")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestICMPv4EchoReply(t *testing.T) {
|
||||||
|
dev := newICMPTestStack(t)
|
||||||
|
src := tcpip.AddrFrom4([4]byte{10, 66, 0, 2})
|
||||||
|
dst := tcpip.AddrFrom4([4]byte{1, 1, 1, 1})
|
||||||
|
payload := []byte("xray wireguard ping")
|
||||||
|
|
||||||
|
icmpMsg := make([]byte, header.ICMPv4MinimumSize+len(payload))
|
||||||
|
req := header.ICMPv4(icmpMsg)
|
||||||
|
req.SetType(header.ICMPv4Echo)
|
||||||
|
req.SetIdent(0x1234)
|
||||||
|
req.SetSequence(7)
|
||||||
|
copy(req.Payload(), payload)
|
||||||
|
req.SetChecksum(header.ICMPv4Checksum(req[:header.ICMPv4MinimumSize], checksum.Checksum(payload, 0)))
|
||||||
|
|
||||||
|
pkt := make([]byte, header.IPv4MinimumSize+len(icmpMsg))
|
||||||
|
ip := header.IPv4(pkt)
|
||||||
|
ip.Encode(&header.IPv4Fields{
|
||||||
|
TotalLength: uint16(len(pkt)),
|
||||||
|
TTL: 64,
|
||||||
|
Protocol: uint8(header.ICMPv4ProtocolNumber),
|
||||||
|
SrcAddr: src,
|
||||||
|
DstAddr: dst,
|
||||||
|
})
|
||||||
|
ip.SetChecksum(^ip.CalculateChecksum())
|
||||||
|
copy(pkt[header.IPv4MinimumSize:], icmpMsg)
|
||||||
|
|
||||||
|
got := startReader(dev)
|
||||||
|
if _, err := dev.Write([][]byte{pkt}, 0); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
reply := header.IPv4(awaitPacket(t, got))
|
||||||
|
if !reply.IsValid(len(reply)) {
|
||||||
|
t.Fatal("invalid ipv4 reply")
|
||||||
|
}
|
||||||
|
if reply.SourceAddress() != dst || reply.DestinationAddress() != src {
|
||||||
|
t.Fatalf("reply addresses %v -> %v, want %v -> %v", reply.SourceAddress(), reply.DestinationAddress(), dst, src)
|
||||||
|
}
|
||||||
|
if reply.TransportProtocol() != header.ICMPv4ProtocolNumber {
|
||||||
|
t.Fatalf("reply protocol %v, want icmpv4", reply.TransportProtocol())
|
||||||
|
}
|
||||||
|
echo := header.ICMPv4(reply.Payload())
|
||||||
|
if echo.Type() != header.ICMPv4EchoReply {
|
||||||
|
t.Fatalf("reply type %v, want echo reply", echo.Type())
|
||||||
|
}
|
||||||
|
if echo.Ident() != 0x1234 || echo.Sequence() != 7 {
|
||||||
|
t.Fatalf("reply ident/seq %#x/%d, want 0x1234/7", echo.Ident(), echo.Sequence())
|
||||||
|
}
|
||||||
|
if !bytes.Equal(echo.Payload(), payload) {
|
||||||
|
t.Fatalf("reply payload %q, want %q", echo.Payload(), payload)
|
||||||
|
}
|
||||||
|
if checksum.Checksum(echo, 0) != 0xffff {
|
||||||
|
t.Fatal("bad icmpv4 checksum")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestICMPv6EchoReply(t *testing.T) {
|
||||||
|
dev := newICMPTestStack(t)
|
||||||
|
src := tcpip.AddrFrom16([16]byte{0xfd, 15: 2})
|
||||||
|
dst := tcpip.AddrFrom16([16]byte{0x26, 0x06, 0x47, 0x00, 0x47, 0x00, 15: 0x11})
|
||||||
|
payload := []byte("xray wireguard ping6")
|
||||||
|
|
||||||
|
icmpMsg := make([]byte, header.ICMPv6MinimumSize+len(payload))
|
||||||
|
req := header.ICMPv6(icmpMsg)
|
||||||
|
req.SetType(header.ICMPv6EchoRequest)
|
||||||
|
req.SetIdent(0x4321)
|
||||||
|
req.SetSequence(9)
|
||||||
|
copy(req.Payload(), payload)
|
||||||
|
req.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
|
||||||
|
Header: req[:header.ICMPv6MinimumSize],
|
||||||
|
Src: src,
|
||||||
|
Dst: dst,
|
||||||
|
PayloadCsum: checksum.Checksum(payload, 0),
|
||||||
|
PayloadLen: len(payload),
|
||||||
|
}))
|
||||||
|
|
||||||
|
pkt := make([]byte, header.IPv6MinimumSize+len(icmpMsg))
|
||||||
|
ip := header.IPv6(pkt)
|
||||||
|
ip.Encode(&header.IPv6Fields{
|
||||||
|
PayloadLength: uint16(len(icmpMsg)),
|
||||||
|
TransportProtocol: header.ICMPv6ProtocolNumber,
|
||||||
|
HopLimit: 64,
|
||||||
|
SrcAddr: src,
|
||||||
|
DstAddr: dst,
|
||||||
|
})
|
||||||
|
copy(pkt[header.IPv6MinimumSize:], icmpMsg)
|
||||||
|
|
||||||
|
got := startReader(dev)
|
||||||
|
if _, err := dev.Write([][]byte{pkt}, 0); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
reply := header.IPv6(awaitPacket(t, got))
|
||||||
|
if !reply.IsValid(len(reply)) {
|
||||||
|
t.Fatal("invalid ipv6 reply")
|
||||||
|
}
|
||||||
|
if reply.SourceAddress() != dst || reply.DestinationAddress() != src {
|
||||||
|
t.Fatalf("reply addresses %v -> %v, want %v -> %v", reply.SourceAddress(), reply.DestinationAddress(), dst, src)
|
||||||
|
}
|
||||||
|
echo := header.ICMPv6(reply.Payload())
|
||||||
|
if echo.Type() != header.ICMPv6EchoReply {
|
||||||
|
t.Fatalf("reply type %v, want echo reply", echo.Type())
|
||||||
|
}
|
||||||
|
if echo.Ident() != 0x4321 || echo.Sequence() != 9 {
|
||||||
|
t.Fatalf("reply ident/seq %#x/%d, want 0x4321/9", echo.Ident(), echo.Sequence())
|
||||||
|
}
|
||||||
|
if !bytes.Equal(echo.Payload(), payload) {
|
||||||
|
t.Fatalf("reply payload %q, want %q", echo.Payload(), payload)
|
||||||
|
}
|
||||||
|
zeroed := header.ICMPv6(append([]byte(nil), echo[:header.ICMPv6MinimumSize]...))
|
||||||
|
zeroed.SetChecksum(0)
|
||||||
|
want := header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
|
||||||
|
Header: zeroed,
|
||||||
|
Src: dst,
|
||||||
|
Dst: src,
|
||||||
|
PayloadCsum: checksum.Checksum(echo.Payload(), 0),
|
||||||
|
PayloadLen: len(echo.Payload()),
|
||||||
|
})
|
||||||
|
if echo.Checksum() != want {
|
||||||
|
t.Fatalf("icmpv6 checksum %#x, want %#x", echo.Checksum(), want)
|
||||||
|
}
|
||||||
|
}
|
||||||
+139
-75
@@ -10,6 +10,7 @@ import (
|
|||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
@@ -17,63 +18,101 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/buf"
|
|
||||||
xnet "github.com/xtls/xray-core/common/net"
|
xnet "github.com/xtls/xray-core/common/net"
|
||||||
xtun "github.com/xtls/xray-core/proxy/tun"
|
|
||||||
"golang.zx2c4.com/wireguard/tun"
|
"golang.zx2c4.com/wireguard/tun"
|
||||||
|
|
||||||
"golang.org/x/net/dns/dnsmessage"
|
"golang.org/x/net/dns/dnsmessage"
|
||||||
|
"gvisor.dev/gvisor/pkg/buffer"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
|
||||||
|
"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/udp"
|
||||||
)
|
)
|
||||||
|
|
||||||
// netTunQueue is how many packets of the stack can wait to be read from its device
|
|
||||||
const netTunQueue = 1024
|
|
||||||
|
|
||||||
// netTun is the device of a TCP/IP stack of Xray: what is written to it goes to the stack,
|
|
||||||
// and what the stack sends is read from it
|
|
||||||
type netTun struct {
|
type netTun struct {
|
||||||
stack xtun.Stack
|
ep *channel.Endpoint
|
||||||
|
stack *stack.Stack
|
||||||
events chan tun.Event
|
events chan tun.Event
|
||||||
// packets are what the stack has sent and Read has not taken yet
|
notifyHandle *channel.NotificationHandle
|
||||||
packets chan *buf.Buffer
|
incomingPacket chan *buffer.View
|
||||||
closed chan struct{}
|
closed chan struct{}
|
||||||
closeOnce sync.Once
|
|
||||||
mtu int
|
mtu int
|
||||||
|
dnsServers []netip.Addr
|
||||||
|
hasV4, hasV6 bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateNetTUN returns a device with a TCP/IP stack on it, and what opens connections through that from
|
func CreateNetTUN(localAddresses, dnsServers []netip.Addr, mtu int, handleLocal bool) (tun.Device, *Net, *stack.Stack, error) {
|
||||||
// localAddresses. With a handler the stack takes every connection that comes from the device, for any address.
|
opts := stack.Options{
|
||||||
// Without one it takes none
|
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
|
||||||
func CreateNetTUN(localAddresses, dnsServers []netip.Addr, mtu int, handler xtun.ConnectionHandler) (tun.Device, *Net, error) {
|
TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4},
|
||||||
|
HandleLocal: handleLocal,
|
||||||
|
}
|
||||||
dev := &netTun{
|
dev := &netTun{
|
||||||
|
ep: channel.New(1024, uint32(mtu), ""),
|
||||||
|
stack: stack.New(opts),
|
||||||
events: make(chan tun.Event, 10),
|
events: make(chan tun.Event, 10),
|
||||||
packets: make(chan *buf.Buffer, netTunQueue),
|
incomingPacket: make(chan *buffer.View),
|
||||||
closed: make(chan struct{}),
|
closed: make(chan struct{}),
|
||||||
|
dnsServers: dnsServers,
|
||||||
mtu: mtu,
|
mtu: mtu,
|
||||||
}
|
}
|
||||||
stack, err := xtun.NewStack(context.Background(), xtun.StackOptions{Tun: dev, MTU: uint32(mtu), Addresses: localAddresses, Remote: true}, handler)
|
sackEnabledOpt := tcpip.TCPSACKEnabled(true) // TCP SACK is disabled by default
|
||||||
if err != nil {
|
tcpipErr := dev.stack.SetTransportProtocolOption(tcp.ProtocolNumber, &sackEnabledOpt)
|
||||||
return nil, nil, err
|
if tcpipErr != nil {
|
||||||
|
return nil, nil, nil, fmt.Errorf("could not enable TCP SACK: %v", tcpipErr)
|
||||||
}
|
}
|
||||||
dev.stack = stack
|
dev.notifyHandle = dev.ep.AddNotify(dev)
|
||||||
|
tcpipErr = dev.stack.CreateNIC(1, dev.ep)
|
||||||
tnet := &Net{
|
if tcpipErr != nil {
|
||||||
DialContextTCPAddrPort: stack.DialTCP,
|
return nil, nil, nil, fmt.Errorf("CreateNIC: %v", tcpipErr)
|
||||||
DialUDPAddrPort: dev.DialUDPAddrPort,
|
|
||||||
dnsServers: dnsServers,
|
|
||||||
}
|
}
|
||||||
for _, ip := range localAddresses {
|
for _, ip := range localAddresses {
|
||||||
|
var protoNumber tcpip.NetworkProtocolNumber
|
||||||
if ip.Is4() {
|
if ip.Is4() {
|
||||||
tnet.hasV4 = true
|
protoNumber = ipv4.ProtocolNumber
|
||||||
} else if ip.Is6() {
|
} else if ip.Is6() {
|
||||||
tnet.hasV6 = true
|
protoNumber = ipv6.ProtocolNumber
|
||||||
}
|
}
|
||||||
|
protoAddr := tcpip.ProtocolAddress{
|
||||||
|
Protocol: protoNumber,
|
||||||
|
AddressWithPrefix: tcpip.AddrFromSlice(ip.AsSlice()).WithPrefix(),
|
||||||
|
}
|
||||||
|
tcpipErr := dev.stack.AddProtocolAddress(1, protoAddr, stack.AddressProperties{})
|
||||||
|
if tcpipErr != nil {
|
||||||
|
return nil, nil, nil, fmt.Errorf("AddProtocolAddress(%v): %v", ip, tcpipErr)
|
||||||
|
}
|
||||||
|
if ip.Is4() {
|
||||||
|
dev.hasV4 = true
|
||||||
|
} else if ip.Is6() {
|
||||||
|
dev.hasV6 = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if dev.hasV4 {
|
||||||
|
dev.stack.AddRoute(tcpip.Route{Destination: header.IPv4EmptySubnet, NIC: 1})
|
||||||
|
}
|
||||||
|
if dev.hasV6 {
|
||||||
|
dev.stack.AddRoute(tcpip.Route{Destination: header.IPv6EmptySubnet, NIC: 1})
|
||||||
|
}
|
||||||
|
|
||||||
|
tnet := &Net{
|
||||||
|
DialContextTCPAddrPort: dev.DialContextTCPAddrPort,
|
||||||
|
DialUDPAddrPort: dev.DialUDPAddrPort,
|
||||||
|
dnsServers: dev.dnsServers,
|
||||||
|
hasV4: dev.hasV4,
|
||||||
|
hasV6: dev.hasV6,
|
||||||
}
|
}
|
||||||
|
|
||||||
dev.events <- tun.EventUp
|
dev.events <- tun.EventUp
|
||||||
return dev, tnet, nil
|
return dev, tnet, dev.stack, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tun *netTun) Name() (string, error) {
|
func (tun *netTun) Name() (string, error) {
|
||||||
@@ -89,62 +128,73 @@ func (tun *netTun) Events() <-chan tun.Event {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (tun *netTun) Read(buf [][]byte, sizes []int, offset int) (int, error) {
|
func (tun *netTun) Read(buf [][]byte, sizes []int, offset int) (int, error) {
|
||||||
|
var view *buffer.View
|
||||||
select {
|
select {
|
||||||
case packet := <-tun.packets:
|
case view = <-tun.incomingPacket:
|
||||||
sizes[0] = copy(buf[0][offset:], packet.Bytes())
|
|
||||||
packet.Release()
|
|
||||||
return 1, nil
|
|
||||||
case <-tun.closed:
|
case <-tun.closed:
|
||||||
return 0, os.ErrClosed
|
return 0, os.ErrClosed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
n, err := view.Read(buf[0][offset:])
|
||||||
|
view.Release()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
sizes[0] = n
|
||||||
|
return 1, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tun *netTun) Write(buf [][]byte, offset int) (int, error) {
|
func (tun *netTun) Write(buf [][]byte, offset int) (int, error) {
|
||||||
for _, buf := range buf {
|
for _, buf := range buf {
|
||||||
tun.stack.Input(buf[offset:])
|
packet := buf[offset:]
|
||||||
|
if len(packet) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pkb := stack.NewPacketBuffer(stack.PacketBufferOptions{Payload: buffer.MakeWithData(packet)})
|
||||||
|
switch packet[0] >> 4 {
|
||||||
|
case 4:
|
||||||
|
tun.ep.InjectInbound(header.IPv4ProtocolNumber, pkb)
|
||||||
|
case 6:
|
||||||
|
tun.ep.InjectInbound(header.IPv6ProtocolNumber, pkb)
|
||||||
|
default:
|
||||||
|
return 0, syscall.EAFNOSUPPORT
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return len(buf), nil
|
return len(buf), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// WritePacket implements xtun.Tun. The packet is in the hands of the stack only until this returns, so it is copied
|
func (tun *netTun) WriteNotify() {
|
||||||
// for Read. The stack does not wait for the one who reads the device, unless that one is far behind: it answers
|
pkt := tun.ep.Read()
|
||||||
// what it is written inside Write, and whoever writes would wait with it and lose what comes meanwhile
|
if pkt == nil {
|
||||||
func (tun *netTun) WritePacket(header, payload []byte) error {
|
return
|
||||||
packet := buf.NewWithSize(int32(len(header) + len(payload)))
|
}
|
||||||
packet.Write(header)
|
|
||||||
packet.Write(payload)
|
view := pkt.ToView()
|
||||||
|
pkt.DecRef()
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case tun.packets <- packet:
|
case tun.incomingPacket <- view:
|
||||||
return nil
|
|
||||||
case <-tun.closed:
|
case <-tun.closed:
|
||||||
packet.Release()
|
view.Release()
|
||||||
return os.ErrClosed
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadPacket implements xtun.Tun. It is not called: the packets are handed to the stack by Write
|
|
||||||
func (tun *netTun) ReadPacket([]byte) ([]byte, error) {
|
|
||||||
<-tun.closed
|
|
||||||
return nil, os.ErrClosed
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start implements xtun.Tun
|
|
||||||
func (tun *netTun) Start() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Index implements xtun.Tun
|
|
||||||
func (tun *netTun) Index() (int, error) {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tun *netTun) Close() error {
|
func (tun *netTun) Close() error {
|
||||||
tun.closeOnce.Do(func() {
|
tun.stack.RemoveNIC(1)
|
||||||
// before the stack ends its connections: nobody may be reading anymore what it writes for that
|
|
||||||
close(tun.closed)
|
|
||||||
tun.stack.Close()
|
tun.stack.Close()
|
||||||
|
tun.ep.RemoveNotify(tun.notifyHandle)
|
||||||
|
tun.ep.Close()
|
||||||
|
|
||||||
|
if tun.events != nil {
|
||||||
close(tun.events)
|
close(tun.events)
|
||||||
})
|
}
|
||||||
|
|
||||||
|
// we don't close incomingPacket, because WriteNotify may be mid-send on it (DNS lookup) and would panic.
|
||||||
|
if tun.closed != nil {
|
||||||
|
close(tun.closed)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,8 +206,17 @@ func (tun *netTun) BatchSize() int {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (tun *netTun) DialContextTCPAddrPort(ctx context.Context, addr netip.AddrPort) (net.Conn, error) {
|
||||||
|
fa, pn := convertToFullAddr(addr)
|
||||||
|
return gonet.DialContextTCP(ctx, tun.stack, fa, pn)
|
||||||
|
}
|
||||||
|
|
||||||
func (tun *netTun) DialUDPAddrPort(laddr, raddr netip.AddrPort) (net.Conn, error) {
|
func (tun *netTun) DialUDPAddrPort(laddr, raddr netip.AddrPort) (net.Conn, error) {
|
||||||
conn, err := tun.stack.DialUDP()
|
var pn tcpip.NetworkProtocolNumber = ipv6.ProtocolNumber
|
||||||
|
if raddr.IsValid() || raddr.Port() > 0 {
|
||||||
|
_, pn = convertToFullAddr(raddr)
|
||||||
|
}
|
||||||
|
conn, err := gonet.DialUDP(tun.stack, nil, nil, pn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -175,6 +234,20 @@ type Net struct {
|
|||||||
cache cache
|
cache cache
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func convertToFullAddr(endpoint netip.AddrPort) (tcpip.FullAddress, tcpip.NetworkProtocolNumber) {
|
||||||
|
var protoNumber tcpip.NetworkProtocolNumber
|
||||||
|
if endpoint.Addr().Is4() {
|
||||||
|
protoNumber = ipv4.ProtocolNumber
|
||||||
|
} else {
|
||||||
|
protoNumber = ipv6.ProtocolNumber
|
||||||
|
}
|
||||||
|
return tcpip.FullAddress{
|
||||||
|
NIC: 1,
|
||||||
|
Addr: tcpip.AddrFromSlice(endpoint.Addr().AsSlice()),
|
||||||
|
Port: endpoint.Port(),
|
||||||
|
}, protoNumber
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errNoSuchHost = errors.New("no such host")
|
errNoSuchHost = errors.New("no such host")
|
||||||
errLameReferral = errors.New("lame referral")
|
errLameReferral = errors.New("lame referral")
|
||||||
@@ -378,8 +451,6 @@ func (tnet *Net) exchange(ctx context.Context, server netip.Addr, q dnsmessage.Q
|
|||||||
return dnsmessage.Parser{}, dnsmessage.Header{}, err
|
return dnsmessage.Parser{}, dnsmessage.Header{}, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// what is opened through the stack of Xray takes no deadline, it ends when it is closed
|
|
||||||
stop := context.AfterFunc(ctx, func() { c.Close() })
|
|
||||||
var p dnsmessage.Parser
|
var p dnsmessage.Parser
|
||||||
var h dnsmessage.Header
|
var h dnsmessage.Header
|
||||||
if useUDP {
|
if useUDP {
|
||||||
@@ -387,15 +458,8 @@ func (tnet *Net) exchange(ctx context.Context, server netip.Addr, q dnsmessage.Q
|
|||||||
} else {
|
} else {
|
||||||
p, h, err = dnsStreamRoundTrip(c, id, q, tcpReq)
|
p, h, err = dnsStreamRoundTrip(c, id, q, tcpReq)
|
||||||
}
|
}
|
||||||
stop()
|
|
||||||
c.Close()
|
c.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if ctx.Err() == context.DeadlineExceeded {
|
|
||||||
// as from a connection that takes a deadline
|
|
||||||
err = &net.OpError{Op: "read", Net: c.RemoteAddr().Network(), Err: os.ErrDeadlineExceeded}
|
|
||||||
} else if ctx.Err() != nil {
|
|
||||||
err = ctx.Err()
|
|
||||||
}
|
|
||||||
if err == context.Canceled {
|
if err == context.Canceled {
|
||||||
err = errCanceled
|
err = errCanceled
|
||||||
} else if err == context.DeadlineExceeded {
|
} else if err == context.DeadlineExceeded {
|
||||||
|
|||||||
@@ -1,310 +0,0 @@
|
|||||||
package wireguard
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
gonet "net"
|
|
||||||
"net/netip"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/net"
|
|
||||||
"golang.org/x/net/dns/dnsmessage"
|
|
||||||
"golang.zx2c4.com/wireguard/tun"
|
|
||||||
)
|
|
||||||
|
|
||||||
type closeHandler struct{}
|
|
||||||
|
|
||||||
func (closeHandler) HandleConnection(conn net.Conn, _ net.Destination) { conn.Close() }
|
|
||||||
|
|
||||||
// onesSum is the Internet checksum of b on top of initial, not inverted.
|
|
||||||
func onesSum(b []byte, initial uint32) uint16 {
|
|
||||||
sum := initial
|
|
||||||
for ; len(b) >= 2; b = b[2:] {
|
|
||||||
sum += uint32(binary.BigEndian.Uint16(b))
|
|
||||||
}
|
|
||||||
if len(b) == 1 {
|
|
||||||
sum += uint32(b[0]) << 8
|
|
||||||
}
|
|
||||||
for sum>>16 != 0 {
|
|
||||||
sum = sum>>16 + sum&0xffff
|
|
||||||
}
|
|
||||||
return uint16(sum)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The inbound answers ICMP echo requests from peers locally, the way the TUN
|
|
||||||
// inbound does: ICMP is not proxied, but ping and connectivity checks through
|
|
||||||
// the tunnel get a reply instead of timing out. The request goes in through
|
|
||||||
// Write and the reply comes out of Read, as the WireGuard device uses them.
|
|
||||||
func TestNetTUNEchoReply(t *testing.T) {
|
|
||||||
for _, test := range []struct {
|
|
||||||
src, dst string
|
|
||||||
request byte
|
|
||||||
reply byte
|
|
||||||
}{
|
|
||||||
{"10.66.0.2", "1.1.1.1", 8, 0},
|
|
||||||
{"fd00::2", "2606:4700:4700::1111", 128, 129},
|
|
||||||
} {
|
|
||||||
dev, _, err := CreateNetTUN([]netip.Addr{netip.MustParseAddr("10.66.0.1"), netip.MustParseAddr("fd00::1")}, nil, 1420, closeHandler{})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
src, dst := netip.MustParseAddr(test.src), netip.MustParseAddr(test.dst)
|
|
||||||
payload := []byte("xray wireguard ping")
|
|
||||||
message := append([]byte{test.request, 0, 0, 0, 0x12, 0x34, 0, 7}, payload...)
|
|
||||||
|
|
||||||
// The stack drops what has a wrong checksum, as what comes through a tunnel has come a long way.
|
|
||||||
var pkt []byte
|
|
||||||
if src.Is4() {
|
|
||||||
pkt = make([]byte, 20, 20+len(message))
|
|
||||||
pkt[0] = 0x45
|
|
||||||
binary.BigEndian.PutUint16(pkt[2:], uint16(20+len(message)))
|
|
||||||
pkt[8], pkt[9] = 64, 1
|
|
||||||
copy(pkt[12:], src.AsSlice())
|
|
||||||
copy(pkt[16:], dst.AsSlice())
|
|
||||||
binary.BigEndian.PutUint16(pkt[10:], ^onesSum(pkt, 0))
|
|
||||||
binary.BigEndian.PutUint16(message[2:], ^onesSum(message, 0))
|
|
||||||
} else {
|
|
||||||
pkt = make([]byte, 40, 40+len(message))
|
|
||||||
pkt[0] = 0x60
|
|
||||||
binary.BigEndian.PutUint16(pkt[4:], uint16(len(message)))
|
|
||||||
pkt[6], pkt[7] = 58, 64
|
|
||||||
copy(pkt[8:], src.AsSlice())
|
|
||||||
copy(pkt[24:], dst.AsSlice())
|
|
||||||
binary.BigEndian.PutUint16(message[2:], ^onesSum(message, uint32(onesSum(pkt[8:40], uint32(len(message))+58))))
|
|
||||||
}
|
|
||||||
pkt = append(pkt, message...)
|
|
||||||
damaged := bytes.Clone(pkt)
|
|
||||||
damaged[len(damaged)-1] ^= 1
|
|
||||||
|
|
||||||
// The stack answers inside Write, the reply waits for whoever reads the device.
|
|
||||||
got := make(chan []byte, 1)
|
|
||||||
const offset = 16
|
|
||||||
go func() {
|
|
||||||
b := make([]byte, 2048)
|
|
||||||
sizes := make([]int, 1)
|
|
||||||
if _, err := dev.Read([][]byte{b}, sizes, offset); err == nil {
|
|
||||||
got <- b[offset : offset+sizes[0]]
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
if _, err := dev.Write([][]byte{append(make([]byte, offset), damaged...), append(make([]byte, offset), pkt...)}, offset); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
var reply []byte
|
|
||||||
select {
|
|
||||||
case reply = <-got:
|
|
||||||
case <-time.After(2 * time.Second):
|
|
||||||
t.Fatal("no echo reply from the stack")
|
|
||||||
}
|
|
||||||
|
|
||||||
header := len(pkt) - len(message)
|
|
||||||
if len(reply) != len(pkt) || reply[0] != pkt[0] {
|
|
||||||
t.Fatalf("reply of %d bytes, version byte %#x", len(reply), reply[0])
|
|
||||||
}
|
|
||||||
var replySrc, replyDst netip.Addr
|
|
||||||
pseudo := uint32(0)
|
|
||||||
if src.Is4() {
|
|
||||||
replySrc, replyDst = netip.AddrFrom4([4]byte(reply[12:16])), netip.AddrFrom4([4]byte(reply[16:20]))
|
|
||||||
if onesSum(reply[:20], 0) != 0xffff || reply[9] != 1 {
|
|
||||||
t.Fatal("bad ipv4 header in the reply")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
replySrc, replyDst = netip.AddrFrom16([16]byte(reply[8:24])), netip.AddrFrom16([16]byte(reply[24:40]))
|
|
||||||
if reply[6] != 58 {
|
|
||||||
t.Fatalf("reply protocol %d, want icmpv6", reply[6])
|
|
||||||
}
|
|
||||||
pseudo = uint32(onesSum(reply[8:40], uint32(len(message))+58))
|
|
||||||
}
|
|
||||||
if replySrc != dst || replyDst != src {
|
|
||||||
t.Fatalf("reply addresses %v -> %v, want %v -> %v", replySrc, replyDst, dst, src)
|
|
||||||
}
|
|
||||||
echo := reply[header:]
|
|
||||||
if echo[0] != test.reply || echo[1] != 0 {
|
|
||||||
t.Fatalf("reply type %d code %d, want echo reply", echo[0], echo[1])
|
|
||||||
}
|
|
||||||
// A reply to the damaged request, which came first, would not have this payload.
|
|
||||||
if !bytes.Equal(echo[4:], message[4:]) {
|
|
||||||
t.Fatalf("reply ident, seq and payload %x, want %x", echo[4:], message[4:])
|
|
||||||
}
|
|
||||||
if onesSum(echo, pseudo) != 0xffff {
|
|
||||||
t.Fatal("bad icmp checksum in the reply")
|
|
||||||
}
|
|
||||||
|
|
||||||
dev.Close()
|
|
||||||
dev.Close()
|
|
||||||
if _, err := dev.Read([][]byte{make([]byte, 2048)}, make([]int, 1), 0); err == nil {
|
|
||||||
t.Fatal("read from a closed device")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// testServer answers what comes through the device of a server: names over UDP port 53, except those that
|
|
||||||
// start with "silent", and everything else with what it was sent.
|
|
||||||
type testServer struct{}
|
|
||||||
|
|
||||||
func (testServer) HandleConnection(conn net.Conn, dest net.Destination) {
|
|
||||||
defer conn.Close()
|
|
||||||
b := make([]byte, 4096)
|
|
||||||
for {
|
|
||||||
n, err := conn.Read(b)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
answer := b[:n]
|
|
||||||
if dest.Network == net.Network_UDP && dest.Port == 53 {
|
|
||||||
var parser dnsmessage.Parser
|
|
||||||
header, err := parser.Start(b[:n])
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
question, err := parser.Question()
|
|
||||||
if err != nil || strings.HasPrefix(question.Name.String(), "silent") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
builder := dnsmessage.NewBuilder(nil, dnsmessage.Header{ID: header.ID, Response: true, RecursionAvailable: true})
|
|
||||||
builder.StartQuestions()
|
|
||||||
builder.Question(question)
|
|
||||||
builder.StartAnswers()
|
|
||||||
builder.AResource(dnsmessage.ResourceHeader{Name: question.Name, Class: dnsmessage.ClassINET, TTL: 60}, dnsmessage.AResource{A: [4]byte{192, 0, 2, 1}})
|
|
||||||
if answer, err = builder.Finish(); err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if _, err := conn.Write(answer); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A client and a server, each with a stack on its device and the two devices joined as a tunnel joins them:
|
|
||||||
// names are resolved, and TCP and UDP go through, as the outbound and the inbound use them.
|
|
||||||
func TestNetTUNClientAndServer(t *testing.T) {
|
|
||||||
server, _, err := CreateNetTUN([]netip.Addr{netip.MustParseAddr("10.66.0.1")}, nil, 1420, testServer{})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
client, tnet, err := CreateNetTUN([]netip.Addr{netip.MustParseAddr("10.66.0.2")}, []netip.Addr{netip.MustParseAddr("10.66.0.53")}, 1420, nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer server.Close()
|
|
||||||
defer client.Close()
|
|
||||||
// As in a tunnel, what reads a device does not wait for the other end to take the packet: a stack answers
|
|
||||||
// inside Write, and its answer has to be read meanwhile.
|
|
||||||
for _, pair := range [][2]tun.Device{{client, server}, {server, client}} {
|
|
||||||
packets := make(chan []byte, 1024)
|
|
||||||
go func() {
|
|
||||||
defer close(packets)
|
|
||||||
bufs, sizes := [][]byte{make([]byte, 2048)}, make([]int, 1)
|
|
||||||
for {
|
|
||||||
if _, err := pair[0].Read(bufs, sizes, 0); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case packets <- bytes.Clone(bufs[0][:sizes[0]]):
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
go func() {
|
|
||||||
for packet := range packets {
|
|
||||||
pair[1].Write([][]byte{packet}, 0)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
if addrs, err := tnet.LookupContextHost(ctx, "xray.example"); err != nil || len(addrs) != 1 || addrs[0] != "192.0.2.1" {
|
|
||||||
t.Fatal("resolved to ", addrs, ", error: ", err)
|
|
||||||
}
|
|
||||||
// a server that does not answer is not waited for longer than the one who asks wants to wait
|
|
||||||
short, cancelShort := context.WithTimeout(ctx, 200*time.Millisecond)
|
|
||||||
start := time.Now()
|
|
||||||
var timeout *gonet.DNSError
|
|
||||||
if addrs, err := tnet.LookupContextHost(short, "silent.example"); !errors.As(err, &timeout) || !timeout.IsTimeout || time.Since(start) > 3*time.Second {
|
|
||||||
t.Fatal("resolved to ", addrs, " after ", time.Since(start), ", error: ", err)
|
|
||||||
}
|
|
||||||
cancelShort()
|
|
||||||
|
|
||||||
sent := make([]byte, 1<<20)
|
|
||||||
rand.Read(sent)
|
|
||||||
for _, network := range []string{"tcp", "udp"} {
|
|
||||||
conn, err := tnet.DialContext(ctx, network, "xray.example:7")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
size := len(sent)
|
|
||||||
if network == "udp" {
|
|
||||||
// more than the tunnel takes in one packet
|
|
||||||
size = 3000
|
|
||||||
}
|
|
||||||
go conn.Write(sent[:size])
|
|
||||||
received := make([]byte, size)
|
|
||||||
conn.SetReadDeadline(time.Now().Add(10 * time.Second))
|
|
||||||
done := make(chan error, 1)
|
|
||||||
go func() {
|
|
||||||
_, err := io.ReadFull(conn, received)
|
|
||||||
done <- err
|
|
||||||
}()
|
|
||||||
select {
|
|
||||||
case err := <-done:
|
|
||||||
if err != nil || !bytes.Equal(received, sent[:size]) {
|
|
||||||
t.Fatal(network, ": received something else than what was sent, error: ", err)
|
|
||||||
}
|
|
||||||
case <-ctx.Done():
|
|
||||||
t.Fatal(network, ": what was sent did not come back")
|
|
||||||
}
|
|
||||||
conn.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A packet of the stack is in its hands only until WritePacket returns, Read gets it as it was then.
|
|
||||||
// A device that is closed is not waited for, also not by a stack that has something to send when it is closed.
|
|
||||||
func TestNetTUNWritePacket(t *testing.T) {
|
|
||||||
dev, tnet, err := CreateNetTUN([]netip.Addr{netip.MustParseAddr("10.66.0.2")}, nil, 1420, nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
b, sizes := make([]byte, 16), make([]int, 1)
|
|
||||||
for i := range 1000 {
|
|
||||||
header, payload := []byte{byte(i), 1, 2, 3}, []byte{4, 5, 6, byte(i)}
|
|
||||||
go func() {
|
|
||||||
dev.(*netTun).WritePacket(header, payload)
|
|
||||||
clear(header)
|
|
||||||
clear(payload)
|
|
||||||
}()
|
|
||||||
if _, err := dev.Read([][]byte{b}, sizes, 0); err != nil || !bytes.Equal(b[:sizes[0]], []byte{byte(i), 1, 2, 3, 4, 5, 6, byte(i)}) {
|
|
||||||
t.Fatal("read ", b[:sizes[0]], " of packet ", i, ", error: ", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// nobody reads now, and no more can wait to be read: a connection waits to send its SYN
|
|
||||||
for range netTunQueue {
|
|
||||||
dev.(*netTun).WritePacket([]byte{1}, nil)
|
|
||||||
}
|
|
||||||
dialed := make(chan error, 1)
|
|
||||||
go func() {
|
|
||||||
_, err := tnet.DialContextTCPAddrPort(context.Background(), netip.MustParseAddrPort("192.0.2.1:80"))
|
|
||||||
dialed <- err
|
|
||||||
}()
|
|
||||||
time.Sleep(50 * time.Millisecond)
|
|
||||||
closed := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
dev.Close()
|
|
||||||
close(closed)
|
|
||||||
}()
|
|
||||||
select {
|
|
||||||
case <-closed:
|
|
||||||
case <-time.After(5 * time.Second):
|
|
||||||
t.Fatal("the device is not closed while a packet waits to be read")
|
|
||||||
}
|
|
||||||
if err := <-dialed; err == nil {
|
|
||||||
t.Fatal("a connection through a closed device")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +1,16 @@
|
|||||||
package tun
|
package wireguard
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"runtime"
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/buf"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const benchBatch = 64
|
const benchBatch = 64
|
||||||
|
|
||||||
// Raw cost of queueing and draining a small burst, as one flow's reader does.
|
// Raw cost of queueing and draining a small burst, as one flow's reader does.
|
||||||
func BenchmarkQueueBurstChan(b *testing.B) {
|
func BenchmarkQueueBurstChan(b *testing.B) {
|
||||||
ch := make(chan *buf.Buffer, udpQueueLimit)
|
ch := make(chan *packet, udpQueueLimit)
|
||||||
p := &buf.Buffer{}
|
p := &packet{}
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
for j := 0; j < benchBatch; j++ {
|
for j := 0; j < benchBatch; j++ {
|
||||||
@@ -26,7 +24,7 @@ func BenchmarkQueueBurstChan(b *testing.B) {
|
|||||||
|
|
||||||
func BenchmarkQueueBurstPacketQueue(b *testing.B) {
|
func BenchmarkQueueBurstPacketQueue(b *testing.B) {
|
||||||
q := newPacketQueue(udpQueueLimit)
|
q := newPacketQueue(udpQueueLimit)
|
||||||
p := &buf.Buffer{}
|
p := &packet{}
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
for j := 0; j < benchBatch; j++ {
|
for j := 0; j < benchBatch; j++ {
|
||||||
@@ -41,8 +39,8 @@ func BenchmarkQueueBurstPacketQueue(b *testing.B) {
|
|||||||
// Producer and consumer on different goroutines; the producer yields when the
|
// Producer and consumer on different goroutines; the producer yields when the
|
||||||
// queue is full instead of spinning, like a blocking channel send would.
|
// queue is full instead of spinning, like a blocking channel send would.
|
||||||
func BenchmarkQueueStreamChan(b *testing.B) {
|
func BenchmarkQueueStreamChan(b *testing.B) {
|
||||||
ch := make(chan *buf.Buffer, udpQueueLimit)
|
ch := make(chan *packet, udpQueueLimit)
|
||||||
p := &buf.Buffer{}
|
p := &packet{}
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
go func() {
|
go func() {
|
||||||
for range ch {
|
for range ch {
|
||||||
@@ -60,7 +58,7 @@ func BenchmarkQueueStreamChan(b *testing.B) {
|
|||||||
|
|
||||||
func BenchmarkQueueStreamPacketQueue(b *testing.B) {
|
func BenchmarkQueueStreamPacketQueue(b *testing.B) {
|
||||||
q := newPacketQueue(udpQueueLimit)
|
q := newPacketQueue(udpQueueLimit)
|
||||||
p := &buf.Buffer{}
|
p := &packet{}
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
+15
-13
@@ -21,13 +21,13 @@ import (
|
|||||||
"github.com/xtls/xray-core/features/policy"
|
"github.com/xtls/xray-core/features/policy"
|
||||||
"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"
|
||||||
xtun "github.com/xtls/xray-core/proxy/tun"
|
|
||||||
"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/stat"
|
"github.com/xtls/xray-core/transport/internet/stat"
|
||||||
"golang.org/x/crypto/curve25519"
|
"golang.org/x/crypto/curve25519"
|
||||||
"golang.zx2c4.com/wireguard/device"
|
"golang.zx2c4.com/wireguard/device"
|
||||||
"golang.zx2c4.com/wireguard/tun"
|
"golang.zx2c4.com/wireguard/tun"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
@@ -44,6 +44,7 @@ type Server struct {
|
|||||||
downlinkCounter stats.Counter
|
downlinkCounter stats.Counter
|
||||||
|
|
||||||
tun tun.Device
|
tun tun.Device
|
||||||
|
stack *stack.Stack
|
||||||
dev *device.Device
|
dev *device.Device
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
|
|
||||||
@@ -94,6 +95,11 @@ func NewServer(ctx context.Context, conf *DeviceConfig) (*Server, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tun, _, stack, err := CreateNetTUN(localAddresses, nil, int(conf.Mtu), false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
pri := common.Must2(ParseKey(conf.SecretKey))
|
pri := common.Must2(ParseKey(conf.SecretKey))
|
||||||
var pub [32]byte
|
var pub [32]byte
|
||||||
curve25519.ScalarBaseMult(&pub, pri)
|
curve25519.ScalarBaseMult(&pub, pri)
|
||||||
@@ -120,15 +126,15 @@ func NewServer(ctx context.Context, conf *DeviceConfig) (*Server, error) {
|
|||||||
uplinkCounter: uplinkCounter,
|
uplinkCounter: uplinkCounter,
|
||||||
downlinkCounter: downlinkCounter,
|
downlinkCounter: downlinkCounter,
|
||||||
|
|
||||||
|
tun: tun,
|
||||||
|
stack: stack,
|
||||||
|
|
||||||
pub: pub,
|
pub: pub,
|
||||||
users: users,
|
users: users,
|
||||||
}
|
}
|
||||||
// The stack hands its connections to s from the first packet on, which the device delivers after Start -> dev.Up.
|
// Install the stack's protocol handlers before the device can deliver packets to it (Start -> dev.Up).
|
||||||
tun, _, err := CreateNetTUN(localAddresses, nil, int(conf.Mtu), s)
|
CreateForwarder(stack, s.HandleConnection)
|
||||||
if err != nil {
|
CreateICMPEchoResponder(stack)
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
s.tun = tun
|
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,7 +261,7 @@ func (s *Server) Start() error {
|
|||||||
if s.src.Address.Family().IsDomain() {
|
if s.src.Address.Family().IsDomain() {
|
||||||
return errors.New("address is domain")
|
return errors.New("address is domain")
|
||||||
}
|
}
|
||||||
listenFunc := func(context.Context) (net.PacketConn, error) {
|
listenFunc := func() (net.PacketConn, error) {
|
||||||
var pktConn net.PacketConn
|
var pktConn net.PacketConn
|
||||||
var err error
|
var err error
|
||||||
if s.streamSettings.FinalMask != nil {
|
if s.streamSettings.FinalMask != nil {
|
||||||
@@ -373,13 +379,9 @@ func (s *Server) HandleConnection(conn net.Conn, dest net.Destination) {
|
|||||||
})
|
})
|
||||||
errors.LogInfo(ctx, "processing from ", source, " to ", dest)
|
errors.LogInfo(ctx, "processing from ", source, " to ", dest)
|
||||||
|
|
||||||
writer := buf.NewWriter(conn)
|
|
||||||
if dest.Network == net.Network_TCP {
|
|
||||||
writer = &xtun.TCPWriter{Writer: writer}
|
|
||||||
}
|
|
||||||
link := &transport.Link{
|
link := &transport.Link{
|
||||||
Reader: &buf.TimeoutWrapperReader{Reader: buf.NewReader(conn)},
|
Reader: &buf.TimeoutWrapperReader{Reader: buf.NewReader(conn)},
|
||||||
Writer: writer,
|
Writer: buf.NewWriter(conn),
|
||||||
}
|
}
|
||||||
if err := s.dispatcher.DispatchLink(ctx, dest, link); err != nil {
|
if err := s.dispatcher.DispatchLink(ctx, dest, link); err != nil {
|
||||||
errors.LogError(ctx, errors.New("connection closed").Base(err))
|
errors.LogError(ctx, errors.New("connection closed").Base(err))
|
||||||
|
|||||||
@@ -1,71 +0,0 @@
|
|||||||
package wireguard
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"io"
|
|
||||||
"net/netip"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
|
||||||
"github.com/xtls/xray-core/common/buf"
|
|
||||||
"github.com/xtls/xray-core/common/net"
|
|
||||||
"github.com/xtls/xray-core/common/protocol"
|
|
||||||
"github.com/xtls/xray-core/features/routing"
|
|
||||||
"github.com/xtls/xray-core/transport"
|
|
||||||
)
|
|
||||||
|
|
||||||
// stackConn is a TCP connection as the stack hands it over: it is its own buf.Writer.
|
|
||||||
type stackConn struct {
|
|
||||||
net.Conn
|
|
||||||
closed atomic.Int32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *stackConn) Read([]byte) (int, error) { return 0, io.EOF }
|
|
||||||
|
|
||||||
func (c *stackConn) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
|
||||||
buf.ReleaseMulti(mb)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *stackConn) Close() error {
|
|
||||||
c.closed.Add(1)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *stackConn) RemoteAddr() net.Addr {
|
|
||||||
return &net.TCPAddr{IP: net.IP{10, 0, 0, 2}, Port: 1234}
|
|
||||||
}
|
|
||||||
|
|
||||||
// closingDispatcher does what an outbound does when the other side has no more to send.
|
|
||||||
type closingDispatcher struct {
|
|
||||||
routing.Dispatcher
|
|
||||||
conn *stackConn
|
|
||||||
closed int32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *closingDispatcher) DispatchLink(_ context.Context, _ net.Destination, link *transport.Link) error {
|
|
||||||
common.Close(link.Writer)
|
|
||||||
d.closed = d.conn.closed.Load()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestServerKeepsConnectionOpenWhenWriterCloses(t *testing.T) {
|
|
||||||
conn := &stackConn{}
|
|
||||||
dispatcher := &closingDispatcher{conn: conn}
|
|
||||||
users := &sync.Map{}
|
|
||||||
users.Store([32]byte{1}, &protocol.MemoryUser{
|
|
||||||
Email: "love@xray.com",
|
|
||||||
Account: &MemoryAccount{AllowedIPs: []netip.Prefix{netip.MustParsePrefix("10.0.0.2/32")}},
|
|
||||||
})
|
|
||||||
s := &Server{ctx: context.Background(), dispatcher: dispatcher, users: users}
|
|
||||||
s.HandleConnection(conn, net.TCPDestination(net.ParseAddress("192.0.2.1"), 443))
|
|
||||||
|
|
||||||
if dispatcher.closed != 0 {
|
|
||||||
t.Fatal("the connection was closed with its writer, while the peer may still be sending")
|
|
||||||
}
|
|
||||||
if got := conn.closed.Load(); got != 1 {
|
|
||||||
t.Fatalf("connection closed %d times, want 1", got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,27 @@
|
|||||||
package wireguard
|
package wireguard
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common/buf"
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"gvisor.dev/gvisor/pkg/buffer"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/checksum"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
|
||||||
|
"gvisor.dev/gvisor/pkg/waiter"
|
||||||
)
|
)
|
||||||
|
|
||||||
func CalculateInterfaceName(name string) (tunName string) {
|
func CalculateInterfaceName(name string) (tunName string) {
|
||||||
@@ -33,3 +48,334 @@ func CalculateInterfaceName(name string) (tunName string) {
|
|||||||
tunName = fmt.Sprintf("%s%d", tunName, tunIndex)
|
tunName = fmt.Sprintf("%s%d", tunName, tunIndex)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CreateForwarder(gstack *stack.Stack, handler func(conn net.Conn, dest net.Destination)) {
|
||||||
|
gstack.SetPromiscuousMode(1, true)
|
||||||
|
gstack.SetSpoofing(1, true)
|
||||||
|
|
||||||
|
tcpForwarder := tcp.NewForwarder(gstack, 0, 65535, func(r *tcp.ForwarderRequest) {
|
||||||
|
go func(r *tcp.ForwarderRequest) {
|
||||||
|
var wq waiter.Queue
|
||||||
|
id := r.ID()
|
||||||
|
|
||||||
|
ep, err := r.CreateEndpoint(&wq)
|
||||||
|
if err != nil {
|
||||||
|
errors.LogError(context.Background(), err.String())
|
||||||
|
r.Complete(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
options := ep.SocketOptions()
|
||||||
|
options.SetKeepAlive(false)
|
||||||
|
options.SetReuseAddress(true)
|
||||||
|
options.SetReusePort(true)
|
||||||
|
|
||||||
|
handler(gonet.NewTCPConn(&wq, ep), net.TCPDestination(net.IPAddress(id.LocalAddress.AsSlice()), net.Port(id.LocalPort)))
|
||||||
|
|
||||||
|
ep.Close()
|
||||||
|
r.Complete(false)
|
||||||
|
}(r)
|
||||||
|
})
|
||||||
|
gstack.SetTransportProtocolHandler(tcp.ProtocolNumber, tcpForwarder.HandlePacket)
|
||||||
|
|
||||||
|
manager := &udpManager{
|
||||||
|
stack: gstack,
|
||||||
|
handler: handler,
|
||||||
|
m: make(map[string]*udpConn),
|
||||||
|
}
|
||||||
|
|
||||||
|
gstack.SetTransportProtocolHandler(udp.ProtocolNumber, func(id stack.TransportEndpointID, pkt *stack.PacketBuffer) bool {
|
||||||
|
data := pkt.Data().AsRange().ToSlice()
|
||||||
|
// if len(data) == 0 {
|
||||||
|
// return false
|
||||||
|
// }
|
||||||
|
srcIP := net.IPAddress(id.RemoteAddress.AsSlice())
|
||||||
|
dstIP := net.IPAddress(id.LocalAddress.AsSlice())
|
||||||
|
if srcIP == nil || dstIP == nil {
|
||||||
|
panic(id)
|
||||||
|
}
|
||||||
|
src := net.UDPDestination(srcIP, net.Port(id.RemotePort))
|
||||||
|
dst := net.UDPDestination(dstIP, net.Port(id.LocalPort))
|
||||||
|
manager.feed(src, dst, data)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type udpManager struct {
|
||||||
|
stack *stack.Stack
|
||||||
|
handler func(conn net.Conn, dest net.Destination)
|
||||||
|
m map[string]*udpConn
|
||||||
|
mutex sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *udpManager) feed(src net.Destination, dst net.Destination, data []byte) {
|
||||||
|
m.mutex.RLock()
|
||||||
|
uc, ok := m.m[src.NetAddr()]
|
||||||
|
if ok {
|
||||||
|
if !uc.queue.push(&packet{p: data, dest: &dst}) {
|
||||||
|
errors.LogDebug(context.Background(), "drop udp with size ", len(data), " to ", dst.NetAddr(), " original ", uc.dst.NetAddr(), " > queue full")
|
||||||
|
}
|
||||||
|
m.mutex.RUnlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.mutex.RUnlock()
|
||||||
|
|
||||||
|
m.mutex.Lock()
|
||||||
|
defer m.mutex.Unlock()
|
||||||
|
|
||||||
|
uc, ok = m.m[src.NetAddr()]
|
||||||
|
if !ok {
|
||||||
|
uc = &udpConn{
|
||||||
|
queue: newPacketQueue(udpQueueLimit),
|
||||||
|
src: src,
|
||||||
|
dst: dst,
|
||||||
|
}
|
||||||
|
uc.writeFunc = m.writeRawUDPPacket
|
||||||
|
uc.closeFunc = func() {
|
||||||
|
m.mutex.Lock()
|
||||||
|
m.close(uc)
|
||||||
|
m.mutex.Unlock()
|
||||||
|
}
|
||||||
|
m.m[src.NetAddr()] = uc
|
||||||
|
go m.handler(uc, dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !uc.queue.push(&packet{p: data, dest: &dst}) {
|
||||||
|
errors.LogDebug(context.Background(), "drop udp with size ", len(data), " to ", dst.NetAddr(), " original ", uc.dst.NetAddr(), " > queue full 2")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *udpManager) close(uc *udpConn) {
|
||||||
|
if !uc.closed {
|
||||||
|
uc.closed = true
|
||||||
|
uc.queue.close()
|
||||||
|
delete(m.m, uc.src.NetAddr())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *udpManager) writeRawUDPPacket(payload []byte, src net.Destination, dst net.Destination) error {
|
||||||
|
udpLen := header.UDPMinimumSize + len(payload)
|
||||||
|
srcIP := tcpip.AddrFromSlice(src.Address.IP())
|
||||||
|
dstIP := tcpip.AddrFromSlice(dst.Address.IP())
|
||||||
|
|
||||||
|
// build packet with appropriate IP header size
|
||||||
|
isIPv4 := dst.Address.Family().IsIPv4()
|
||||||
|
ipHdrSize := header.IPv6MinimumSize
|
||||||
|
ipProtocol := header.IPv6ProtocolNumber
|
||||||
|
if isIPv4 {
|
||||||
|
ipHdrSize = header.IPv4MinimumSize
|
||||||
|
ipProtocol = header.IPv4ProtocolNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||||
|
ReserveHeaderBytes: ipHdrSize + header.UDPMinimumSize,
|
||||||
|
Payload: buffer.MakeWithData(payload),
|
||||||
|
})
|
||||||
|
defer pkt.DecRef()
|
||||||
|
|
||||||
|
// Build UDP header
|
||||||
|
udpHdr := header.UDP(pkt.TransportHeader().Push(header.UDPMinimumSize))
|
||||||
|
udpHdr.Encode(&header.UDPFields{
|
||||||
|
SrcPort: uint16(src.Port),
|
||||||
|
DstPort: uint16(dst.Port),
|
||||||
|
Length: uint16(udpLen),
|
||||||
|
})
|
||||||
|
|
||||||
|
// Calculate and set UDP checksum
|
||||||
|
xsum := header.PseudoHeaderChecksum(header.UDPProtocolNumber, srcIP, dstIP, uint16(udpLen))
|
||||||
|
udpHdr.SetChecksum(^udpHdr.CalculateChecksum(checksum.Checksum(payload, xsum)))
|
||||||
|
|
||||||
|
// Build IP header
|
||||||
|
if isIPv4 {
|
||||||
|
ipHdr := header.IPv4(pkt.NetworkHeader().Push(header.IPv4MinimumSize))
|
||||||
|
ipHdr.Encode(&header.IPv4Fields{
|
||||||
|
TotalLength: uint16(header.IPv4MinimumSize + udpLen),
|
||||||
|
TTL: 64,
|
||||||
|
Protocol: uint8(header.UDPProtocolNumber),
|
||||||
|
SrcAddr: srcIP,
|
||||||
|
DstAddr: dstIP,
|
||||||
|
})
|
||||||
|
ipHdr.SetChecksum(^ipHdr.CalculateChecksum())
|
||||||
|
} else {
|
||||||
|
ipHdr := header.IPv6(pkt.NetworkHeader().Push(header.IPv6MinimumSize))
|
||||||
|
ipHdr.Encode(&header.IPv6Fields{
|
||||||
|
PayloadLength: uint16(udpLen),
|
||||||
|
TransportProtocol: header.UDPProtocolNumber,
|
||||||
|
HopLimit: 64,
|
||||||
|
SrcAddr: srcIP,
|
||||||
|
DstAddr: dstIP,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// dispatch the packet
|
||||||
|
err := m.stack.WriteRawPacket(1, ipProtocol, buffer.MakeWithView(pkt.ToView()))
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("failed to write raw udp packet back to stack err ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type packet struct {
|
||||||
|
p []byte
|
||||||
|
dest *net.Destination
|
||||||
|
}
|
||||||
|
|
||||||
|
type udpConn struct {
|
||||||
|
queue *packetQueue
|
||||||
|
src net.Destination
|
||||||
|
dst net.Destination
|
||||||
|
writeFunc func(payload []byte, src net.Destination, dst net.Destination) error
|
||||||
|
closeFunc func()
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *udpConn) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
||||||
|
for {
|
||||||
|
q, ok := c.queue.pop()
|
||||||
|
if !ok {
|
||||||
|
return nil, io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
b := buf.New()
|
||||||
|
if _, err := b.Write(q.p); err != nil {
|
||||||
|
errors.LogErrorInner(context.Background(), err, "drop packet to ", q.dest, " with size ", len(q.p))
|
||||||
|
b.Release()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
b.UDP = q.dest
|
||||||
|
|
||||||
|
return buf.MultiBuffer{b}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *udpConn) Read(p []byte) (int, error) {
|
||||||
|
q, ok := c.queue.pop()
|
||||||
|
if !ok {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
n := copy(p, q.p)
|
||||||
|
if n != len(q.p) {
|
||||||
|
return 0, io.ErrShortBuffer
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *udpConn) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
||||||
|
for i, b := range mb {
|
||||||
|
dst := c.dst
|
||||||
|
if b.UDP != nil {
|
||||||
|
if b.UDP.Address.Family().IsDomain() {
|
||||||
|
errors.LogError(context.Background(), "impossible domain packet ", b.UDP, " reply via original target ", dst)
|
||||||
|
} else {
|
||||||
|
dst = *b.UDP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err := c.writeFunc(b.Bytes(), dst, c.src)
|
||||||
|
if err != nil {
|
||||||
|
buf.ReleaseMulti(mb[i:])
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
b.Release()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *udpConn) Write(p []byte) (int, error) {
|
||||||
|
err := c.writeFunc(p, c.dst, c.src)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *udpConn) Close() error {
|
||||||
|
c.closeFunc()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *udpConn) LocalAddr() net.Addr {
|
||||||
|
return c.dst.RawNetAddr()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *udpConn) RemoteAddr() net.Addr {
|
||||||
|
return c.src.RawNetAddr()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *udpConn) SetDeadline(t time.Time) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *udpConn) SetReadDeadline(t time.Time) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *udpConn) SetWriteDeadline(t time.Time) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// udpQueueLimit bounds the packets waiting for one UDP flow; more are dropped.
|
||||||
|
const udpQueueLimit = 1024
|
||||||
|
|
||||||
|
// packetQueue holds the packets waiting for one UDP flow. Unlike a buffered
|
||||||
|
// channel of the same bound it only allocates for packets actually queued, so
|
||||||
|
// the many idle flows kept until the idle timeout cost next to nothing.
|
||||||
|
type packetQueue struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
items []*packet
|
||||||
|
limit int
|
||||||
|
notify chan struct{}
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPacketQueue(limit int) *packetQueue {
|
||||||
|
return &packetQueue{limit: limit, notify: make(chan struct{}, 1)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// push queues p and reports whether it was accepted.
|
||||||
|
func (q *packetQueue) push(p *packet) bool {
|
||||||
|
q.mu.Lock()
|
||||||
|
defer q.mu.Unlock()
|
||||||
|
if q.closed || len(q.items) >= q.limit {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
q.items = append(q.items, p)
|
||||||
|
select {
|
||||||
|
case q.notify <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// pop blocks until a packet is queued or the queue is closed and drained.
|
||||||
|
func (q *packetQueue) pop() (*packet, bool) {
|
||||||
|
for {
|
||||||
|
q.mu.Lock()
|
||||||
|
if len(q.items) > 0 {
|
||||||
|
p := q.items[0]
|
||||||
|
q.items[0] = nil
|
||||||
|
q.items = q.items[1:]
|
||||||
|
if len(q.items) == 0 {
|
||||||
|
q.items = nil
|
||||||
|
}
|
||||||
|
q.mu.Unlock()
|
||||||
|
return p, true
|
||||||
|
}
|
||||||
|
if q.closed {
|
||||||
|
q.mu.Unlock()
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
q.mu.Unlock()
|
||||||
|
<-q.notify
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *packetQueue) close() {
|
||||||
|
q.mu.Lock()
|
||||||
|
defer q.mu.Unlock()
|
||||||
|
if !q.closed {
|
||||||
|
q.closed = true
|
||||||
|
close(q.notify)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,48 +1,48 @@
|
|||||||
package tun
|
package wireguard
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/buf"
|
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BenchmarkUDPNewSession measures what one new UDP flow costs the
|
// BenchmarkUDPManagerNewSession measures what one new UDP flow costs the
|
||||||
// inbound while it stays open: QUIC and DNS open many short flows, and each
|
// inbound while it stays open: QUIC and DNS open many short flows, and each
|
||||||
// one lives until the connection idle timeout.
|
// one lives until the connection idle timeout.
|
||||||
func BenchmarkUDPNewSession(b *testing.B) {
|
func BenchmarkUDPManagerNewSession(b *testing.B) {
|
||||||
u := newUdpConnectionHandler(func(conn net.Conn, dest net.Destination) {}, nil)
|
m := &udpManager{
|
||||||
|
handler: func(conn net.Conn, dest net.Destination) {},
|
||||||
|
m: make(map[string]*udpConn),
|
||||||
|
}
|
||||||
dst := net.UDPDestination(net.ParseAddress("1.1.1.1"), 443)
|
dst := net.UDPDestination(net.ParseAddress("1.1.1.1"), 443)
|
||||||
|
payload := make([]byte, 1200)
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
src := net.UDPDestination(net.IPAddress([]byte{10, byte(i >> 16), byte(i >> 8), byte(i)}), net.Port(1024+i%60000))
|
src := net.UDPDestination(net.IPAddress([]byte{10, byte(i >> 16), byte(i >> 8), byte(i)}), net.Port(1024+i%60000))
|
||||||
u.HandlePacket(src, dst, &buf.Buffer{})
|
m.feed(src, dst, payload)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPacketQueueOrderAndClose(t *testing.T) {
|
func TestPacketQueueOrderAndClose(t *testing.T) {
|
||||||
q := newPacketQueue(udpQueueLimit)
|
q := newPacketQueue(udpQueueLimit)
|
||||||
for i := 0; i < 3; i++ {
|
for i := 0; i < 3; i++ {
|
||||||
b := buf.New()
|
if !q.push(&packet{p: []byte{byte(i)}}) {
|
||||||
b.WriteByte(byte(i))
|
|
||||||
if !q.push(b) {
|
|
||||||
t.Fatalf("push %d rejected", i)
|
t.Fatalf("push %d rejected", i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i := 0; i < 3; i++ {
|
for i := 0; i < 3; i++ {
|
||||||
b, ok := q.pop()
|
p, ok := q.pop()
|
||||||
if !ok || b.Byte(0) != byte(i) {
|
if !ok || p.p[0] != byte(i) {
|
||||||
t.Fatalf("pop %d: got %v, %v", i, b, ok)
|
t.Fatalf("pop %d: got %v, %v", i, p, ok)
|
||||||
}
|
}
|
||||||
b.Release()
|
|
||||||
}
|
}
|
||||||
q.close()
|
q.close()
|
||||||
if _, ok := q.pop(); ok {
|
if _, ok := q.pop(); ok {
|
||||||
t.Fatal("pop after close returned a packet")
|
t.Fatal("pop after close returned a packet")
|
||||||
}
|
}
|
||||||
if q.push(&buf.Buffer{}) {
|
if q.push(&packet{}) {
|
||||||
t.Fatal("push after close accepted")
|
t.Fatal("push after close accepted")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -50,11 +50,11 @@ func TestPacketQueueOrderAndClose(t *testing.T) {
|
|||||||
func TestPacketQueueLimit(t *testing.T) {
|
func TestPacketQueueLimit(t *testing.T) {
|
||||||
q := newPacketQueue(udpQueueLimit)
|
q := newPacketQueue(udpQueueLimit)
|
||||||
for i := 0; i < udpQueueLimit; i++ {
|
for i := 0; i < udpQueueLimit; i++ {
|
||||||
if !q.push(&buf.Buffer{}) {
|
if !q.push(&packet{}) {
|
||||||
t.Fatalf("push %d rejected below the limit", i)
|
t.Fatalf("push %d rejected below the limit", i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if q.push(&buf.Buffer{}) {
|
if q.push(&packet{}) {
|
||||||
t.Fatal("push above the limit accepted")
|
t.Fatal("push above the limit accepted")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,7 +80,7 @@ func TestPacketQueueCloseUnblocksReader(t *testing.T) {
|
|||||||
func TestPacketQueueDropsDrainedStorage(t *testing.T) {
|
func TestPacketQueueDropsDrainedStorage(t *testing.T) {
|
||||||
q := newPacketQueue(udpQueueLimit)
|
q := newPacketQueue(udpQueueLimit)
|
||||||
for i := 0; i < 100; i++ {
|
for i := 0; i < 100; i++ {
|
||||||
q.push(&buf.Buffer{})
|
q.push(&packet{})
|
||||||
}
|
}
|
||||||
for i := 0; i < 100; i++ {
|
for i := 0; i < 100; i++ {
|
||||||
q.pop()
|
q.pop()
|
||||||
@@ -16,10 +16,8 @@ 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/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"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/core"
|
"github.com/xtls/xray-core/core"
|
||||||
"github.com/xtls/xray-core/proxy/dokodemo"
|
|
||||||
"github.com/xtls/xray-core/proxy/freedom"
|
"github.com/xtls/xray-core/proxy/freedom"
|
||||||
v2http "github.com/xtls/xray-core/proxy/http"
|
v2http "github.com/xtls/xray-core/proxy/http"
|
||||||
v2httptest "github.com/xtls/xray-core/testing/servers/http"
|
v2httptest "github.com/xtls/xray-core/testing/servers/http"
|
||||||
@@ -379,88 +377,3 @@ func TestHttpBasicAuth(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Server-first protocols (SMTP, FTP, MySQL) send nothing from the client until the server's
|
|
||||||
// greeting arrives, so the HTTP outbound must not wait for a first payload before dialing.
|
|
||||||
func TestHTTPOutboundServerFirst(t *testing.T) {
|
|
||||||
tcpServer := tcp.Server{
|
|
||||||
MsgProcessor: xor,
|
|
||||||
SendFirst: []byte("send first"),
|
|
||||||
}
|
|
||||||
dest, err := tcpServer.Start()
|
|
||||||
common.Must(err)
|
|
||||||
defer tcpServer.Close()
|
|
||||||
|
|
||||||
serverPort := tcp.PickPort()
|
|
||||||
serverConfig := &core.Config{
|
|
||||||
Inbound: []*core.InboundHandlerConfig{
|
|
||||||
{
|
|
||||||
ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
|
|
||||||
PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},
|
|
||||||
Listen: net.NewIPOrDomain(net.LocalHostIP),
|
|
||||||
}),
|
|
||||||
ProxySettings: serial.ToTypedMessage(&v2http.ServerConfig{}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Outbound: []*core.OutboundHandlerConfig{
|
|
||||||
{
|
|
||||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
|
||||||
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
clientPort := tcp.PickPort()
|
|
||||||
clientConfig := &core.Config{
|
|
||||||
Inbound: []*core.InboundHandlerConfig{
|
|
||||||
{
|
|
||||||
ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
|
|
||||||
PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},
|
|
||||||
Listen: net.NewIPOrDomain(net.LocalHostIP),
|
|
||||||
}),
|
|
||||||
ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
|
|
||||||
RewriteAddress: net.NewIPOrDomain(dest.Address),
|
|
||||||
RewritePort: uint32(dest.Port),
|
|
||||||
AllowedNetworks: []net.Network{net.Network_TCP},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Outbound: []*core.OutboundHandlerConfig{
|
|
||||||
{
|
|
||||||
ProxySettings: serial.ToTypedMessage(&v2http.ClientConfig{
|
|
||||||
Server: &protocol.ServerEndpoint{
|
|
||||||
Address: net.NewIPOrDomain(net.LocalHostIP),
|
|
||||||
Port: uint32(serverPort),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
servers, err := InitializeServerConfigs(serverConfig, clientConfig)
|
|
||||||
common.Must(err)
|
|
||||||
defer CloseAllServers(servers)
|
|
||||||
|
|
||||||
conn, err := net.DialTCP("tcp", nil, &net.TCPAddr{
|
|
||||||
IP: []byte{127, 0, 0, 1},
|
|
||||||
Port: int(clientPort),
|
|
||||||
})
|
|
||||||
common.Must(err)
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
common.Must(conn.SetReadDeadline(time.Now().Add(5 * time.Second)))
|
|
||||||
response := make([]byte, 1024)
|
|
||||||
nBytes, err := conn.Read(response)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("no server greeting through HTTP outbound: ", err)
|
|
||||||
}
|
|
||||||
if string(response[:nBytes]) != "send first" {
|
|
||||||
t.Fatal("unexpected first response: ", string(response[:nBytes]))
|
|
||||||
}
|
|
||||||
common.Must(conn.SetReadDeadline(time.Time{}))
|
|
||||||
|
|
||||||
if err := testTCPConn2(conn, 1024, time.Second*5)(); err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ import (
|
|||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
"golang.org/x/net/http2/hpack"
|
"golang.org/x/net/http2/hpack"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/app/log"
|
"github.com/xtls/xray-core/app/log"
|
||||||
"github.com/xtls/xray-core/app/proxyman"
|
"github.com/xtls/xray-core/app/proxyman"
|
||||||
@@ -60,30 +64,53 @@ const (
|
|||||||
masqueAuthorization = "Basic dUBleGFtcGxlLmNvbTpw"
|
masqueAuthorization = "Basic dUBleGFtcGxlLmNvbTpw"
|
||||||
)
|
)
|
||||||
|
|
||||||
// masqueEcho answers what comes to the echo port of the server, TCP and UDP alike
|
func startMasqueServer(t *testing.T, h2 bool) (net.Port, [32]byte) {
|
||||||
type masqueEcho struct{}
|
dev, _, gstack, err := wireguard.CreateNetTUN([]netip.Addr{masqueServerV4, masqueServerV6}, nil, transmasque.MinPacketSize, false)
|
||||||
|
common.Must(err)
|
||||||
|
t.Cleanup(func() { dev.Close() })
|
||||||
|
|
||||||
func (masqueEcho) HandleConnection(conn net.Conn, dest net.Destination) {
|
for _, addr := range []netip.Addr{masqueServerV4, masqueServerV6} {
|
||||||
defer conn.Close()
|
proto := ipv4.ProtocolNumber
|
||||||
if dest.Port != masqueEchoPort {
|
if addr.Is6() {
|
||||||
return
|
proto = ipv6.ProtocolNumber
|
||||||
}
|
}
|
||||||
b := make([]byte, 2048)
|
local := tcpip.FullAddress{NIC: 1, Addr: tcpip.AddrFromSlice(addr.AsSlice()), Port: masqueEchoPort}
|
||||||
|
l, err := gonet.ListenTCP(gstack, local, proto)
|
||||||
|
common.Must(err)
|
||||||
|
go func() {
|
||||||
for {
|
for {
|
||||||
n, err := conn.Read(b)
|
c, err := l.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if _, err := conn.Write(xor(b[:n])); err != nil {
|
go func() {
|
||||||
|
defer c.Close()
|
||||||
|
b := make([]byte, 2048)
|
||||||
|
for {
|
||||||
|
n, err := c.Read(b)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err := c.Write(xor(b[:n])); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
}()
|
||||||
func startMasqueServer(t *testing.T, h2 bool) (net.Port, [32]byte) {
|
u, err := gonet.DialUDP(gstack, &local, nil, proto)
|
||||||
dev, _, err := wireguard.CreateNetTUN([]netip.Addr{masqueServerV4, masqueServerV6}, nil, transmasque.MinPacketSize, masqueEcho{})
|
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
t.Cleanup(func() { dev.Close() })
|
go func() {
|
||||||
|
b := make([]byte, 2048)
|
||||||
|
for {
|
||||||
|
n, addr, err := u.ReadFrom(b)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
u.WriteTo(xor(b[:n]), addr)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
var current atomic.Pointer[connectip.Conn]
|
var current atomic.Pointer[connectip.Conn]
|
||||||
go func() {
|
go func() {
|
||||||
|
|||||||
@@ -1,17 +1,14 @@
|
|||||||
package scenarios
|
package scenarios
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
//"time"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/app/log"
|
"github.com/xtls/xray-core/app/log"
|
||||||
"github.com/xtls/xray-core/app/proxyman"
|
"github.com/xtls/xray-core/app/proxyman"
|
||||||
"github.com/xtls/xray-core/app/router"
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
clog "github.com/xtls/xray-core/common/log"
|
clog "github.com/xtls/xray-core/common/log"
|
||||||
"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/serial"
|
"github.com/xtls/xray-core/common/serial"
|
||||||
core "github.com/xtls/xray-core/core"
|
core "github.com/xtls/xray-core/core"
|
||||||
"github.com/xtls/xray-core/infra/conf"
|
"github.com/xtls/xray-core/infra/conf"
|
||||||
@@ -20,18 +17,9 @@ import (
|
|||||||
"github.com/xtls/xray-core/proxy/wireguard"
|
"github.com/xtls/xray-core/proxy/wireguard"
|
||||||
"github.com/xtls/xray-core/testing/servers/tcp"
|
"github.com/xtls/xray-core/testing/servers/tcp"
|
||||||
"github.com/xtls/xray-core/testing/servers/udp"
|
"github.com/xtls/xray-core/testing/servers/udp"
|
||||||
"golang.org/x/crypto/curve25519"
|
//"golang.org/x/sync/errgroup"
|
||||||
"golang.org/x/sync/errgroup"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func wireguardPublicKey(private string) string {
|
|
||||||
key, err := wireguard.ParseKey(private)
|
|
||||||
common.Must(err)
|
|
||||||
var public [32]byte
|
|
||||||
curve25519.ScalarBaseMult(&public, key)
|
|
||||||
return hex.EncodeToString(public[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWireguard(t *testing.T) {
|
func TestWireguard(t *testing.T) {
|
||||||
tcpServer := tcp.Server{
|
tcpServer := tcp.Server{
|
||||||
MsgProcessor: xor,
|
MsgProcessor: xor,
|
||||||
@@ -40,44 +28,18 @@ func TestWireguard(t *testing.T) {
|
|||||||
common.Must(err)
|
common.Must(err)
|
||||||
defer tcpServer.Close()
|
defer tcpServer.Close()
|
||||||
|
|
||||||
udpServer := udp.Server{
|
|
||||||
MsgProcessor: xor,
|
|
||||||
}
|
|
||||||
udpDest, err := udpServer.Start()
|
|
||||||
common.Must(err)
|
|
||||||
defer udpServer.Close()
|
|
||||||
|
|
||||||
serverPrivate, _ := conf.ParseWireGuardKey("EGs4lTSJPmgELx6YiJAmPR2meWi6bY+e9rTdCipSj10=")
|
serverPrivate, _ := conf.ParseWireGuardKey("EGs4lTSJPmgELx6YiJAmPR2meWi6bY+e9rTdCipSj10=")
|
||||||
|
serverPublic, _ := conf.ParseWireGuardKey("osAMIyil18HeZXGGBDC9KpZoM+L2iGyXWVSYivuM9B0=")
|
||||||
clientPrivate, _ := conf.ParseWireGuardKey("CPQSpgxgdQRZa5SUbT3HLv+mmDVHLW5YR/rQlzum/2I=")
|
clientPrivate, _ := conf.ParseWireGuardKey("CPQSpgxgdQRZa5SUbT3HLv+mmDVHLW5YR/rQlzum/2I=")
|
||||||
|
clientPublic, _ := conf.ParseWireGuardKey("MmLJ5iHFVVBp7VsB0hxfpQ0wEzAbT2KQnpQpj0+RtBw=")
|
||||||
|
|
||||||
// What the client asks for is somewhere else, the stack of the server takes nothing that is for this machine.
|
|
||||||
redirect := func(tag string, dest net.Destination) *core.OutboundHandlerConfig {
|
|
||||||
return &core.OutboundHandlerConfig{
|
|
||||||
Tag: tag,
|
|
||||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
|
||||||
DestinationOverride: &freedom.DestinationOverride{
|
|
||||||
Server: &protocol.ServerEndpoint{
|
|
||||||
Address: net.NewIPOrDomain(dest.Address),
|
|
||||||
Port: uint32(dest.Port),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
serverPort := udp.PickPort()
|
serverPort := udp.PickPort()
|
||||||
serverConfig := &core.Config{
|
serverConfig := &core.Config{
|
||||||
App: []*serial.TypedMessage{
|
App: []*serial.TypedMessage{
|
||||||
serial.ToTypedMessage(&log.Config{
|
serial.ToTypedMessage(&log.Config{
|
||||||
ErrorLogLevel: clog.Severity_Warning,
|
ErrorLogLevel: clog.Severity_Debug,
|
||||||
ErrorLogType: log.LogType_Console,
|
ErrorLogType: log.LogType_Console,
|
||||||
}),
|
}),
|
||||||
serial.ToTypedMessage(&router.Config{
|
|
||||||
Rule: []*router.RoutingRule{
|
|
||||||
{Networks: []net.Network{net.Network_TCP}, TargetTag: &router.RoutingRule_Tag{Tag: "tcp"}},
|
|
||||||
{Networks: []net.Network{net.Network_UDP}, TargetTag: &router.RoutingRule_Tag{Tag: "udp"}},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
Inbound: []*core.InboundHandlerConfig{
|
Inbound: []*core.InboundHandlerConfig{
|
||||||
{
|
{
|
||||||
@@ -87,64 +49,59 @@ func TestWireguard(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
ProxySettings: serial.ToTypedMessage(&wireguard.DeviceConfig{
|
ProxySettings: serial.ToTypedMessage(&wireguard.DeviceConfig{
|
||||||
IsClient: false,
|
IsClient: false,
|
||||||
Endpoint: []string{"10.0.0.1", "fd00::1"},
|
NoKernelTun: false,
|
||||||
|
Endpoint: []string{"10.0.0.1"},
|
||||||
Mtu: 1420,
|
Mtu: 1420,
|
||||||
SecretKey: serverPrivate,
|
SecretKey: serverPrivate,
|
||||||
Users: []*protocol.User{{
|
Peers: []*wireguard.PeerConfig{{
|
||||||
Email: "love@xray.com",
|
PublicKey: serverPublic,
|
||||||
Account: serial.ToTypedMessage(&wireguard.PeerConfig{
|
AllowedIps: []string{"0.0.0.0/0", "::0/0"},
|
||||||
PublicKey: wireguardPublicKey(clientPrivate),
|
|
||||||
AllowedIps: []string{"10.0.0.2/32", "fd00::2/128"},
|
|
||||||
}),
|
|
||||||
}},
|
}},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Outbound: []*core.OutboundHandlerConfig{
|
Outbound: []*core.OutboundHandlerConfig{
|
||||||
redirect("tcp", dest),
|
{
|
||||||
redirect("udp", udpDest),
|
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||||
|
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
|
||||||
|
}),
|
||||||
|
SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{}),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
clientPort := tcp.PickPort()
|
clientPort := tcp.PickPort()
|
||||||
client6Port := tcp.PickPort()
|
clientConfig := &core.Config{
|
||||||
clientUDPPort := udp.PickPort()
|
App: []*serial.TypedMessage{
|
||||||
dokodemoInbound := func(port net.Port, dest net.Destination) *core.InboundHandlerConfig {
|
serial.ToTypedMessage(&log.Config{
|
||||||
return &core.InboundHandlerConfig{
|
ErrorLogLevel: clog.Severity_Debug,
|
||||||
|
ErrorLogType: log.LogType_Console,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Inbound: []*core.InboundHandlerConfig{
|
||||||
|
{
|
||||||
ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
|
ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
|
||||||
PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(port)}},
|
PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},
|
||||||
Listen: net.NewIPOrDomain(net.LocalHostIP),
|
Listen: net.NewIPOrDomain(net.LocalHostIP),
|
||||||
}),
|
}),
|
||||||
ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
|
ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
|
||||||
RewriteAddress: net.NewIPOrDomain(dest.Address),
|
RewriteAddress: net.NewIPOrDomain(dest.Address),
|
||||||
RewritePort: uint32(dest.Port),
|
RewritePort: uint32(dest.Port),
|
||||||
AllowedNetworks: []net.Network{dest.Network},
|
AllowedNetworks: []net.Network{net.Network_TCP},
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
clientConfig := &core.Config{
|
|
||||||
App: []*serial.TypedMessage{
|
|
||||||
serial.ToTypedMessage(&log.Config{
|
|
||||||
ErrorLogLevel: clog.Severity_Warning,
|
|
||||||
ErrorLogType: log.LogType_Console,
|
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
Inbound: []*core.InboundHandlerConfig{
|
|
||||||
dokodemoInbound(clientPort, net.TCPDestination(net.ParseAddress("192.0.2.1"), 7)),
|
|
||||||
dokodemoInbound(client6Port, net.TCPDestination(net.ParseAddress("2001:db8::1"), 7)),
|
|
||||||
dokodemoInbound(clientUDPPort, net.UDPDestination(net.ParseAddress("192.0.2.1"), 7)),
|
|
||||||
},
|
},
|
||||||
Outbound: []*core.OutboundHandlerConfig{
|
Outbound: []*core.OutboundHandlerConfig{
|
||||||
{
|
{
|
||||||
ProxySettings: serial.ToTypedMessage(&wireguard.DeviceConfig{
|
ProxySettings: serial.ToTypedMessage(&wireguard.DeviceConfig{
|
||||||
IsClient: true,
|
IsClient: true,
|
||||||
NoKernelTun: true,
|
NoKernelTun: false,
|
||||||
Endpoint: []string{"10.0.0.2", "fd00::2"},
|
Endpoint: []string{"10.0.0.2"},
|
||||||
Mtu: 1420,
|
Mtu: 1420,
|
||||||
SecretKey: clientPrivate,
|
SecretKey: clientPrivate,
|
||||||
Peers: []*wireguard.PeerConfig{{
|
Peers: []*wireguard.PeerConfig{{
|
||||||
Endpoint: "127.0.0.1:" + serverPort.String(),
|
Endpoint: "127.0.0.1:" + serverPort.String(),
|
||||||
PublicKey: wireguardPublicKey(serverPrivate),
|
PublicKey: clientPublic,
|
||||||
AllowedIps: []string{"0.0.0.0/0", "::0/0"},
|
AllowedIps: []string{"0.0.0.0/0", "::0/0"},
|
||||||
}},
|
}},
|
||||||
}),
|
}),
|
||||||
@@ -157,13 +114,13 @@ func TestWireguard(t *testing.T) {
|
|||||||
common.Must(err)
|
common.Must(err)
|
||||||
defer CloseAllServers(servers)
|
defer CloseAllServers(servers)
|
||||||
|
|
||||||
var errg errgroup.Group
|
// FIXME: for some reason wg server does not receive
|
||||||
for range 3 {
|
|
||||||
errg.Go(testTCPConn(clientPort, 1024*1024, time.Second*20))
|
// var errg errgroup.Group
|
||||||
}
|
// for i := 0; i < 1; i++ {
|
||||||
errg.Go(testTCPConn(client6Port, 1024*1024, time.Second*20))
|
// errg.Go(testTCPConn(clientPort, 1024, time.Second*2))
|
||||||
errg.Go(testUDPConn(clientUDPPort, 1024, time.Second*5))
|
// }
|
||||||
if err := errg.Wait(); err != nil {
|
// if err := errg.Wait(); err != nil {
|
||||||
t.Error(err)
|
// t.Error(err)
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
package filter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/xtls/xray-core/common/geodata"
|
|
||||||
"github.com/xtls/xray-core/common/net"
|
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *Config) buildFilter() (*filter, error) {
|
|
||||||
if len(c.BlockedSourceIps) == 0 && len(c.AllowedSourceIps) == 0 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
f := new(filter)
|
|
||||||
var err error
|
|
||||||
if len(c.BlockedSourceIps) > 0 {
|
|
||||||
f.blocked, err = geodata.IPReg.BuildIPMatcher(c.BlockedSourceIps)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(c.AllowedSourceIps) > 0 {
|
|
||||||
f.allowed, err = geodata.IPReg.BuildIPMatcher(c.AllowedSourceIps)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return f, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) Listen(inner net.Listener) (net.Listener, error) {
|
|
||||||
f, err := c.buildFilter()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if f == nil {
|
|
||||||
return inner, nil
|
|
||||||
}
|
|
||||||
return &listener{Listener: inner, filter: f}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) WrapConnClient(conn net.Conn, dest *net.Destination, dialer *finalmask.Dialer) (net.Conn, error) {
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) WrapConnServer(conn net.Conn) (net.Conn, error) {
|
|
||||||
// TCP filtering is installed by Listen, before per-connection masks.
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
|
|
||||||
f, err := c.buildFilter()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if f == nil {
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
return &packetConn{PacketConn: conn, filter: f}, nil
|
|
||||||
}
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
|
||||||
// versions:
|
|
||||||
// protoc-gen-go v1.36.11
|
|
||||||
// protoc v6.33.5
|
|
||||||
// source: transport/internet/finalmask/filter/config.proto
|
|
||||||
|
|
||||||
package filter
|
|
||||||
|
|
||||||
import (
|
|
||||||
geodata "github.com/xtls/xray-core/common/geodata"
|
|
||||||
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 Config struct {
|
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
|
||||||
BlockedSourceIps []*geodata.IPRule `protobuf:"bytes,1,rep,name=blocked_source_ips,json=blockedSourceIps,proto3" json:"blocked_source_ips,omitempty"`
|
|
||||||
AllowedSourceIps []*geodata.IPRule `protobuf:"bytes,2,rep,name=allowed_source_ips,json=allowedSourceIps,proto3" json:"allowed_source_ips,omitempty"`
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Config) Reset() {
|
|
||||||
*x = Config{}
|
|
||||||
mi := &file_transport_internet_finalmask_filter_config_proto_msgTypes[0]
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Config) String() string {
|
|
||||||
return protoimpl.X.MessageStringOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*Config) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (x *Config) ProtoReflect() protoreflect.Message {
|
|
||||||
mi := &file_transport_internet_finalmask_filter_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 Config.ProtoReflect.Descriptor instead.
|
|
||||||
func (*Config) Descriptor() ([]byte, []int) {
|
|
||||||
return file_transport_internet_finalmask_filter_config_proto_rawDescGZIP(), []int{0}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Config) GetBlockedSourceIps() []*geodata.IPRule {
|
|
||||||
if x != nil {
|
|
||||||
return x.BlockedSourceIps
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Config) GetAllowedSourceIps() []*geodata.IPRule {
|
|
||||||
if x != nil {
|
|
||||||
return x.AllowedSourceIps
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var File_transport_internet_finalmask_filter_config_proto protoreflect.FileDescriptor
|
|
||||||
|
|
||||||
const file_transport_internet_finalmask_filter_config_proto_rawDesc = "" +
|
|
||||||
"\n" +
|
|
||||||
"0transport/internet/finalmask/filter/config.proto\x12(xray.transport.internet.finalmask.filter\x1a\x1bcommon/geodata/geodat.proto\"\x9e\x01\n" +
|
|
||||||
"\x06Config\x12I\n" +
|
|
||||||
"\x12blocked_source_ips\x18\x01 \x03(\v2\x1b.xray.common.geodata.IPRuleR\x10blockedSourceIps\x12I\n" +
|
|
||||||
"\x12allowed_source_ips\x18\x02 \x03(\v2\x1b.xray.common.geodata.IPRuleR\x10allowedSourceIpsB\x9a\x01\n" +
|
|
||||||
",com.xray.transport.internet.finalmask.filterP\x01Z=github.com/xtls/xray-core/transport/internet/finalmask/filter\xaa\x02(Xray.Transport.Internet.Finalmask.Filterb\x06proto3"
|
|
||||||
|
|
||||||
var (
|
|
||||||
file_transport_internet_finalmask_filter_config_proto_rawDescOnce sync.Once
|
|
||||||
file_transport_internet_finalmask_filter_config_proto_rawDescData []byte
|
|
||||||
)
|
|
||||||
|
|
||||||
func file_transport_internet_finalmask_filter_config_proto_rawDescGZIP() []byte {
|
|
||||||
file_transport_internet_finalmask_filter_config_proto_rawDescOnce.Do(func() {
|
|
||||||
file_transport_internet_finalmask_filter_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_filter_config_proto_rawDesc), len(file_transport_internet_finalmask_filter_config_proto_rawDesc)))
|
|
||||||
})
|
|
||||||
return file_transport_internet_finalmask_filter_config_proto_rawDescData
|
|
||||||
}
|
|
||||||
|
|
||||||
var file_transport_internet_finalmask_filter_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
|
||||||
var file_transport_internet_finalmask_filter_config_proto_goTypes = []any{
|
|
||||||
(*Config)(nil), // 0: xray.transport.internet.finalmask.filter.Config
|
|
||||||
(*geodata.IPRule)(nil), // 1: xray.common.geodata.IPRule
|
|
||||||
}
|
|
||||||
var file_transport_internet_finalmask_filter_config_proto_depIdxs = []int32{
|
|
||||||
1, // 0: xray.transport.internet.finalmask.filter.Config.blocked_source_ips:type_name -> xray.common.geodata.IPRule
|
|
||||||
1, // 1: xray.transport.internet.finalmask.filter.Config.allowed_source_ips:type_name -> xray.common.geodata.IPRule
|
|
||||||
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 extension type_name
|
|
||||||
2, // [2:2] is the sub-list for extension extendee
|
|
||||||
0, // [0:2] is the sub-list for field type_name
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() { file_transport_internet_finalmask_filter_config_proto_init() }
|
|
||||||
func file_transport_internet_finalmask_filter_config_proto_init() {
|
|
||||||
if File_transport_internet_finalmask_filter_config_proto != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
type x struct{}
|
|
||||||
out := protoimpl.TypeBuilder{
|
|
||||||
File: protoimpl.DescBuilder{
|
|
||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
|
||||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_filter_config_proto_rawDesc), len(file_transport_internet_finalmask_filter_config_proto_rawDesc)),
|
|
||||||
NumEnums: 0,
|
|
||||||
NumMessages: 1,
|
|
||||||
NumExtensions: 0,
|
|
||||||
NumServices: 0,
|
|
||||||
},
|
|
||||||
GoTypes: file_transport_internet_finalmask_filter_config_proto_goTypes,
|
|
||||||
DependencyIndexes: file_transport_internet_finalmask_filter_config_proto_depIdxs,
|
|
||||||
MessageInfos: file_transport_internet_finalmask_filter_config_proto_msgTypes,
|
|
||||||
}.Build()
|
|
||||||
File_transport_internet_finalmask_filter_config_proto = out.File
|
|
||||||
file_transport_internet_finalmask_filter_config_proto_goTypes = nil
|
|
||||||
file_transport_internet_finalmask_filter_config_proto_depIdxs = nil
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package xray.transport.internet.finalmask.filter;
|
|
||||||
option csharp_namespace = "Xray.Transport.Internet.Finalmask.Filter";
|
|
||||||
option go_package = "github.com/xtls/xray-core/transport/internet/finalmask/filter";
|
|
||||||
option java_package = "com.xray.transport.internet.finalmask.filter";
|
|
||||||
option java_multiple_files = true;
|
|
||||||
|
|
||||||
import "common/geodata/geodat.proto";
|
|
||||||
|
|
||||||
message Config {
|
|
||||||
repeated xray.common.geodata.IPRule blocked_source_ips = 1;
|
|
||||||
repeated xray.common.geodata.IPRule allowed_source_ips = 2;
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
package filter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/xtls/xray-core/common/geodata"
|
|
||||||
"github.com/xtls/xray-core/common/net"
|
|
||||||
)
|
|
||||||
|
|
||||||
type listener struct {
|
|
||||||
net.Listener
|
|
||||||
filter *filter
|
|
||||||
}
|
|
||||||
|
|
||||||
// Accept filters connections after the TCP handshake and before higher-level processing.
|
|
||||||
func (l *listener) Accept() (net.Conn, error) {
|
|
||||||
for {
|
|
||||||
conn, err := l.Listener.Accept()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if l.filter.rejects(conn.RemoteAddr()) {
|
|
||||||
conn.Close()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type packetConn struct {
|
|
||||||
net.PacketConn
|
|
||||||
filter *filter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *packetConn) ReadFrom(p []byte) (int, net.Addr, error) {
|
|
||||||
for {
|
|
||||||
n, addr, err := c.PacketConn.ReadFrom(p)
|
|
||||||
// Reads may return both data and an error; filter the data first.
|
|
||||||
if c.filter.rejects(addr) {
|
|
||||||
if err != nil {
|
|
||||||
return 0, nil, err
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return n, addr, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type filter struct {
|
|
||||||
blocked geodata.IPMatcher
|
|
||||||
allowed geodata.IPMatcher
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *filter) rejects(addr net.Addr) bool {
|
|
||||||
var ip net.IP
|
|
||||||
switch addr := addr.(type) {
|
|
||||||
case *net.TCPAddr:
|
|
||||||
ip = addr.IP
|
|
||||||
case *net.UDPAddr:
|
|
||||||
ip = addr.IP
|
|
||||||
case *net.IPAddr:
|
|
||||||
ip = addr.IP
|
|
||||||
default:
|
|
||||||
// Unknown sources cannot satisfy an allowlist.
|
|
||||||
return f.allowed != nil
|
|
||||||
}
|
|
||||||
if f.blocked != nil && f.blocked.Match(ip) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return f.allowed != nil && !f.allowed.Match(ip)
|
|
||||||
}
|
|
||||||
@@ -1,311 +0,0 @@
|
|||||||
package filter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/pires/go-proxyproto"
|
|
||||||
"github.com/xtls/xray-core/common/geodata"
|
|
||||||
xnet "github.com/xtls/xray-core/common/net"
|
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestTCPFilterBeforeOtherMasks(t *testing.T) {
|
|
||||||
for _, policy := range testPolicies(t) {
|
|
||||||
t.Run(policy.name, func(t *testing.T) {
|
|
||||||
blocked4 := sourceConn("192.0.2.1")
|
|
||||||
blocked6 := sourceConn("2001:db8::1")
|
|
||||||
unknown := &testConn{addr: &net.UnixAddr{Name: "filter.sock", Net: "unix"}}
|
|
||||||
missing := new(testConn)
|
|
||||||
allowed := sourceConn("198.51.100.1")
|
|
||||||
allowed6 := sourceConn("2001:db8:1::1")
|
|
||||||
raw := &testListener{conns: []net.Conn{blocked4, blocked6, unknown, missing, allowed, allowed6}}
|
|
||||||
spy := new(recordMask)
|
|
||||||
fm := finalmask.NewFinalMask([]finalmask.TCPMask{spy, policy.config}, nil, nil,
|
|
||||||
func(context.Context, net.Addr) (net.Listener, error) { return raw, nil }, nil, nil)
|
|
||||||
l, err := fm.Listen(context.Background(), &net.TCPAddr{})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer l.Close()
|
|
||||||
whitelist := len(policy.config.AllowedSourceIps) > 0
|
|
||||||
want := []net.Conn{allowed, allowed6}
|
|
||||||
if !whitelist {
|
|
||||||
want = append([]net.Conn{unknown, missing}, want...)
|
|
||||||
}
|
|
||||||
for _, expected := range want {
|
|
||||||
if got, err := l.Accept(); err != nil || got != expected {
|
|
||||||
t.Fatalf("Accept() = (%v, %v), want %v", got, err, expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Fake sockets panic on Read or Write. Rejection must not require handshake data.
|
|
||||||
if !blocked4.closed || !blocked6.closed || unknown.closed != whitelist || missing.closed != whitelist || allowed.closed || allowed6.closed || raw.closed {
|
|
||||||
t.Fatal("filter did not close only the blocked connections")
|
|
||||||
}
|
|
||||||
if len(spy.seen) != len(want) {
|
|
||||||
t.Fatal("blocked connections reached the next mask")
|
|
||||||
}
|
|
||||||
for i, conn := range spy.seen {
|
|
||||||
if conn != want[i] {
|
|
||||||
t.Fatal("unexpected connection reached the next mask")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if conn, err := l.Accept(); conn != nil || !errors.Is(err, io.EOF) {
|
|
||||||
t.Fatalf("listener error was not propagated: (%v, %v)", conn, err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTCPFilterUsesProxyHeader(t *testing.T) {
|
|
||||||
for _, test := range []struct {
|
|
||||||
name, peer, reported string
|
|
||||||
blocked bool
|
|
||||||
}{
|
|
||||||
{"blocked_header", "198.51.100.1", "192.0.2.1", true},
|
|
||||||
{"allowed_header", "192.0.2.1", "198.51.100.1", false},
|
|
||||||
} {
|
|
||||||
t.Run(test.name, func(t *testing.T) {
|
|
||||||
server, client := net.Pipe()
|
|
||||||
defer server.Close()
|
|
||||||
defer client.Close()
|
|
||||||
if err := server.SetDeadline(time.Now().Add(5 * time.Second)); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
peer := sourceConn(test.peer)
|
|
||||||
peer.Conn = server
|
|
||||||
conn := proxyproto.NewConn(peer)
|
|
||||||
next := sourceConn("198.51.100.2")
|
|
||||||
l, err := testConfig(t, "192.0.2.0/24").Listen(&testListener{conns: []net.Conn{conn, next}})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer l.Close()
|
|
||||||
written := make(chan error, 1)
|
|
||||||
go func() {
|
|
||||||
_, err := client.Write([]byte("PROXY TCP4 " + test.reported + " 203.0.113.1 1234 443\r\n"))
|
|
||||||
written <- err
|
|
||||||
}()
|
|
||||||
got, err := l.Accept()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := <-written; err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
var want net.Conn = conn
|
|
||||||
if test.blocked {
|
|
||||||
want = next
|
|
||||||
}
|
|
||||||
if got != want || peer.closed != test.blocked {
|
|
||||||
t.Fatal("filter did not follow the address supplied by PROXY protocol")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUDPFilter(t *testing.T) {
|
|
||||||
for _, policy := range testPolicies(t) {
|
|
||||||
t.Run(policy.name, func(t *testing.T) {
|
|
||||||
allowed := &net.UDPAddr{IP: net.ParseIP("198.51.100.1"), Port: 1234}
|
|
||||||
allowed6 := &net.UDPAddr{IP: net.ParseIP("2001:db8:1::1"), Port: 1234}
|
|
||||||
unknown := datagram{data: []byte("unknown"), addr: &net.UnixAddr{Name: "filter.sock", Net: "unixgram"}}
|
|
||||||
missing := datagram{data: []byte("missing address")}
|
|
||||||
raw := &testPacketConn{reads: []datagram{
|
|
||||||
{data: []byte("blocked"), addr: &net.UDPAddr{IP: net.ParseIP("192.0.2.1"), Port: 1234}},
|
|
||||||
{addr: &net.UDPAddr{IP: net.ParseIP("2001:db8::1"), Port: 1234}},
|
|
||||||
unknown,
|
|
||||||
missing,
|
|
||||||
{addr: allowed},
|
|
||||||
{data: []byte("allowed"), addr: allowed},
|
|
||||||
{data: []byte("allowed6"), addr: allowed6},
|
|
||||||
{data: []byte("blocked again"), addr: &net.UDPAddr{IP: net.ParseIP("192.0.2.2"), Port: 1234}},
|
|
||||||
}}
|
|
||||||
fm := finalmask.NewFinalMask(nil, []finalmask.UDPMask{policy.config}, nil, nil, nil,
|
|
||||||
func(context.Context, net.Addr) (net.PacketConn, error) { return raw, nil })
|
|
||||||
conn, err := fm.ListenPacket(context.Background(), &net.UDPAddr{})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
buf := make([]byte, 64)
|
|
||||||
if len(policy.config.AllowedSourceIps) == 0 {
|
|
||||||
for _, expected := range []datagram{unknown, missing} {
|
|
||||||
n, addr, err := conn.ReadFrom(buf)
|
|
||||||
if err != nil || addr != expected.addr || string(buf[:n]) != string(expected.data) {
|
|
||||||
t.Fatalf("unknown source was not allowed by blacklist: (%q, %v, %v)", buf[:n], addr, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if n, addr, err := conn.ReadFrom(buf); n != 0 || addr != allowed || err != nil {
|
|
||||||
t.Fatalf("zero-length allowed datagram was lost: (%d, %v, %v)", n, addr, err)
|
|
||||||
}
|
|
||||||
n, addr, err := conn.ReadFrom(buf)
|
|
||||||
if err != nil || addr != allowed || string(buf[:n]) != "allowed" {
|
|
||||||
t.Fatalf("ReadFrom() = (%q, %v, %v)", buf[:n], addr, err)
|
|
||||||
}
|
|
||||||
n, addr, err = conn.ReadFrom(buf)
|
|
||||||
if err != nil || addr != allowed6 || string(buf[:n]) != "allowed6" {
|
|
||||||
t.Fatalf("IPv6 ReadFrom() = (%q, %v, %v)", buf[:n], addr, err)
|
|
||||||
}
|
|
||||||
if _, _, err := conn.ReadFrom(buf); !errors.Is(err, io.EOF) {
|
|
||||||
t.Fatalf("underlying read error was not propagated: %v", err)
|
|
||||||
}
|
|
||||||
if raw.closed {
|
|
||||||
t.Fatal("dropping a packet closed the shared UDP socket")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEmptyFilter(t *testing.T) {
|
|
||||||
for _, empty := range []*Config{{}, {AllowedSourceIps: []*geodata.IPRule{}}} {
|
|
||||||
rawListener := new(testListener)
|
|
||||||
rawPacket := new(testPacketConn)
|
|
||||||
if l, err := empty.Listen(rawListener); err != nil || l != rawListener {
|
|
||||||
t.Fatal("an empty TCP filter should preserve the listener")
|
|
||||||
}
|
|
||||||
if conn, err := empty.WrapPacketConnServer(rawPacket, nil, nil); err != nil || conn != rawPacket {
|
|
||||||
t.Fatal("an empty UDP filter should preserve the socket")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUDPFilterReadError(t *testing.T) {
|
|
||||||
for _, policy := range testPolicies(t) {
|
|
||||||
t.Run(policy.name, func(t *testing.T) {
|
|
||||||
for _, test := range []struct {
|
|
||||||
name string
|
|
||||||
source net.Addr
|
|
||||||
blocked bool
|
|
||||||
}{
|
|
||||||
{"blocked", &net.UDPAddr{IP: net.ParseIP("192.0.2.1")}, true},
|
|
||||||
{"allowed", &net.UDPAddr{IP: net.ParseIP("198.51.100.1")}, false},
|
|
||||||
{"missing address", nil, len(policy.config.AllowedSourceIps) > 0},
|
|
||||||
{"unknown address", &net.UnixAddr{Name: "filter.sock", Net: "unixgram"}, len(policy.config.AllowedSourceIps) > 0},
|
|
||||||
} {
|
|
||||||
t.Run(test.name, func(t *testing.T) {
|
|
||||||
source := test.source
|
|
||||||
raw := &testPacketConn{reads: []datagram{{data: []byte("partial"), addr: source, err: io.ErrShortBuffer}}}
|
|
||||||
conn, err := policy.config.WrapPacketConnServer(raw, nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
buf := make([]byte, 32)
|
|
||||||
n, addr, err := conn.ReadFrom(buf)
|
|
||||||
if !errors.Is(err, io.ErrShortBuffer) {
|
|
||||||
t.Fatalf("read error was not preserved: %v", err)
|
|
||||||
}
|
|
||||||
if test.blocked {
|
|
||||||
if n != 0 || addr != nil {
|
|
||||||
t.Fatal("partial data from a blocked source was returned with an error")
|
|
||||||
}
|
|
||||||
} else if n != 7 || addr != source || string(buf[:n]) != "partial" {
|
|
||||||
t.Fatal("partial data from an unblocked source was changed")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testPolicies(t *testing.T) []struct {
|
|
||||||
name string
|
|
||||||
config *Config
|
|
||||||
} {
|
|
||||||
t.Helper()
|
|
||||||
blocked := testConfig(t, "192.0.2.0/24", "2001:db8::/48").BlockedSourceIps
|
|
||||||
allowed := testConfig(t, "198.51.100.0/24", "2001:db8:1::/48").BlockedSourceIps
|
|
||||||
broader := testConfig(t, "192.0.2.0/24", "198.51.100.0/24", "2001:db8::/32").BlockedSourceIps
|
|
||||||
return []struct {
|
|
||||||
name string
|
|
||||||
config *Config
|
|
||||||
}{
|
|
||||||
{"blacklist", &Config{BlockedSourceIps: blocked}},
|
|
||||||
{"whitelist", &Config{AllowedSourceIps: allowed}},
|
|
||||||
{"both", &Config{BlockedSourceIps: blocked, AllowedSourceIps: broader}},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testConfig(t *testing.T, entries ...string) *Config {
|
|
||||||
t.Helper()
|
|
||||||
rules, err := geodata.ParseIPRules(entries)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
return &Config{BlockedSourceIps: rules}
|
|
||||||
}
|
|
||||||
|
|
||||||
type testConn struct {
|
|
||||||
net.Conn
|
|
||||||
addr net.Addr
|
|
||||||
closed bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func sourceConn(ip string) *testConn {
|
|
||||||
return &testConn{addr: &net.TCPAddr{IP: net.ParseIP(ip), Port: 1234}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *testConn) RemoteAddr() net.Addr { return c.addr }
|
|
||||||
func (c *testConn) Close() error {
|
|
||||||
c.closed = true
|
|
||||||
if c.Conn != nil {
|
|
||||||
return c.Conn.Close()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type testListener struct {
|
|
||||||
net.Listener
|
|
||||||
conns []net.Conn
|
|
||||||
closed bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *testListener) Accept() (net.Conn, error) {
|
|
||||||
if len(l.conns) == 0 {
|
|
||||||
return nil, io.EOF
|
|
||||||
}
|
|
||||||
conn := l.conns[0]
|
|
||||||
l.conns = l.conns[1:]
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *testListener) Close() error { l.closed = true; return nil }
|
|
||||||
|
|
||||||
type datagram struct {
|
|
||||||
data []byte
|
|
||||||
addr net.Addr
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
type testPacketConn struct {
|
|
||||||
net.PacketConn
|
|
||||||
reads []datagram
|
|
||||||
closed bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *testPacketConn) ReadFrom(p []byte) (int, net.Addr, error) {
|
|
||||||
if len(c.reads) == 0 {
|
|
||||||
return 0, nil, io.EOF
|
|
||||||
}
|
|
||||||
packet := c.reads[0]
|
|
||||||
c.reads = c.reads[1:]
|
|
||||||
return copy(p, packet.data), packet.addr, packet.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *testPacketConn) Close() error { c.closed = true; return nil }
|
|
||||||
|
|
||||||
type recordMask struct{ seen []net.Conn }
|
|
||||||
|
|
||||||
func (m *recordMask) WrapConnClient(c net.Conn, _ *xnet.Destination, _ *finalmask.Dialer) (net.Conn, error) {
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *recordMask) WrapConnServer(c net.Conn) (net.Conn, error) {
|
|
||||||
m.seen = append(m.seen, c)
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
@@ -255,7 +255,6 @@ type Config struct {
|
|||||||
ResetMin int64 `protobuf:"varint,1,opt,name=reset_min,json=resetMin,proto3" json:"reset_min,omitempty"`
|
ResetMin int64 `protobuf:"varint,1,opt,name=reset_min,json=resetMin,proto3" json:"reset_min,omitempty"`
|
||||||
ResetMax int64 `protobuf:"varint,2,opt,name=reset_max,json=resetMax,proto3" json:"reset_max,omitempty"`
|
ResetMax int64 `protobuf:"varint,2,opt,name=reset_max,json=resetMax,proto3" json:"reset_max,omitempty"`
|
||||||
Items []*Item `protobuf:"bytes,3,rep,name=items,proto3" json:"items,omitempty"`
|
Items []*Item `protobuf:"bytes,3,rep,name=items,proto3" json:"items,omitempty"`
|
||||||
Script string `protobuf:"bytes,4,opt,name=script,proto3" json:"script,omitempty"`
|
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
@@ -311,13 +310,6 @@ func (x *Config) GetItems() []*Item {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Config) GetScript() string {
|
|
||||||
if x != nil {
|
|
||||||
return x.Script
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
var File_transport_internet_finalmask_noise_config_proto protoreflect.FileDescriptor
|
var File_transport_internet_finalmask_noise_config_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
const file_transport_internet_finalmask_noise_config_proto_rawDesc = "" +
|
const file_transport_internet_finalmask_noise_config_proto_rawDesc = "" +
|
||||||
@@ -345,12 +337,11 @@ const file_transport_internet_finalmask_noise_config_proto_rawDesc = "" +
|
|||||||
"\x06packet\x18\x05 \x01(\fR\x06packet\x12\x1b\n" +
|
"\x06packet\x18\x05 \x01(\fR\x06packet\x12\x1b\n" +
|
||||||
"\tdelay_min\x18\x06 \x01(\x03R\bdelayMin\x12\x1b\n" +
|
"\tdelay_min\x18\x06 \x01(\x03R\bdelayMin\x12\x1b\n" +
|
||||||
"\tdelay_max\x18\a \x01(\x03R\bdelayMax\x12L\n" +
|
"\tdelay_max\x18\a \x01(\x03R\bdelayMax\x12L\n" +
|
||||||
"\bsegments\x18\b \x03(\v20.xray.transport.internet.finalmask.noise.SegmentR\bsegments\"\x9f\x01\n" +
|
"\bsegments\x18\b \x03(\v20.xray.transport.internet.finalmask.noise.SegmentR\bsegments\"\x87\x01\n" +
|
||||||
"\x06Config\x12\x1b\n" +
|
"\x06Config\x12\x1b\n" +
|
||||||
"\treset_min\x18\x01 \x01(\x03R\bresetMin\x12\x1b\n" +
|
"\treset_min\x18\x01 \x01(\x03R\bresetMin\x12\x1b\n" +
|
||||||
"\treset_max\x18\x02 \x01(\x03R\bresetMax\x12C\n" +
|
"\treset_max\x18\x02 \x01(\x03R\bresetMax\x12C\n" +
|
||||||
"\x05items\x18\x03 \x03(\v2-.xray.transport.internet.finalmask.noise.ItemR\x05items\x12\x16\n" +
|
"\x05items\x18\x03 \x03(\v2-.xray.transport.internet.finalmask.noise.ItemR\x05itemsB\x97\x01\n" +
|
||||||
"\x06script\x18\x04 \x01(\tR\x06scriptB\x97\x01\n" +
|
|
||||||
"+com.xray.transport.internet.finalmask.noiseP\x01Z<github.com/xtls/xray-core/transport/internet/finalmask/noise\xaa\x02'Xray.Transport.Internet.Finalmask.Noiseb\x06proto3"
|
"+com.xray.transport.internet.finalmask.noiseP\x01Z<github.com/xtls/xray-core/transport/internet/finalmask/noise\xaa\x02'Xray.Transport.Internet.Finalmask.Noiseb\x06proto3"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -37,5 +37,4 @@ message Config {
|
|||||||
int64 reset_min = 1;
|
int64 reset_min = 1;
|
||||||
int64 reset_max = 2;
|
int64 reset_max = 2;
|
||||||
repeated Item items = 3;
|
repeated Item items = 3;
|
||||||
string script = 4;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package noise
|
package noise
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"net"
|
"net"
|
||||||
@@ -11,7 +10,6 @@ import (
|
|||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/crypto"
|
"github.com/xtls/xray-core/common/crypto"
|
||||||
"github.com/xtls/xray-core/common/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const asciiLetters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
const asciiLetters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
@@ -19,27 +17,17 @@ const asciiLetters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|||||||
type noiseConn struct {
|
type noiseConn struct {
|
||||||
net.PacketConn
|
net.PacketConn
|
||||||
config *Config
|
config *Config
|
||||||
script *scriptEngine
|
|
||||||
noise []scriptPacket
|
|
||||||
m map[string]time.Time
|
m map[string]time.Time
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
counter atomic.Uint32
|
counter atomic.Uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConnClient(c *Config, raw net.PacketConn) (net.PacketConn, error) {
|
func NewConnClient(c *Config, raw net.PacketConn) (net.PacketConn, error) {
|
||||||
conn := &noiseConn{
|
return &noiseConn{
|
||||||
PacketConn: raw,
|
PacketConn: raw,
|
||||||
config: c,
|
config: c,
|
||||||
m: make(map[string]time.Time),
|
m: make(map[string]time.Time),
|
||||||
}
|
}, nil
|
||||||
if c.Script != "" {
|
|
||||||
engine, err := c.engine()
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("failed to initialize the noise script ", c.Script).Base(err)
|
|
||||||
}
|
|
||||||
conn.script = engine
|
|
||||||
}
|
|
||||||
return conn, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) {
|
func NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) {
|
||||||
@@ -109,30 +97,13 @@ func (c *noiseConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
|||||||
t := c.m[addr.String()]
|
t := c.m[addr.String()]
|
||||||
|
|
||||||
if t.IsZero() || (c.config.ResetMax > 0 && time.Now().After(t)) {
|
if t.IsZero() || (c.config.ResetMax > 0 && time.Now().After(t)) {
|
||||||
c.sendNoise(addr)
|
for _, item := range c.config.Items {
|
||||||
|
c.PacketConn.WriteTo(c.buildPacket(item), addr)
|
||||||
|
time.Sleep(time.Duration(crypto.RandBetween(item.DelayMin, item.DelayMax)) * time.Millisecond)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.m[addr.String()] = time.Now().Add(time.Duration(crypto.RandBetween(c.config.ResetMin, c.config.ResetMax)) * time.Second)
|
c.m[addr.String()] = time.Now().Add(time.Duration(crypto.RandBetween(c.config.ResetMin, c.config.ResetMax)) * time.Second)
|
||||||
|
|
||||||
return c.PacketConn.WriteTo(p, addr)
|
return c.PacketConn.WriteTo(p, addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *noiseConn) sendNoise(addr net.Addr) {
|
|
||||||
if c.script != nil {
|
|
||||||
packets, err := c.script.packets(addr.String(), c.noise[:0])
|
|
||||||
if err != nil {
|
|
||||||
errors.LogWarningInner(context.Background(), err, "the noise script produced no packets")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.noise = packets
|
|
||||||
for _, packet := range packets {
|
|
||||||
c.PacketConn.WriteTo(packet.packet, addr)
|
|
||||||
time.Sleep(packet.delay)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, item := range c.config.Items {
|
|
||||||
c.PacketConn.WriteTo(c.buildPacket(item), addr)
|
|
||||||
time.Sleep(time.Duration(crypto.RandBetween(item.DelayMin, item.DelayMax)) * time.Millisecond)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,148 +0,0 @@
|
|||||||
package noise
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/crypto"
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
|
||||||
xlua "github.com/xtls/xray-core/common/lua"
|
|
||||||
lua "github.com/yuin/gopher-lua"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
valueLimit = 65535
|
|
||||||
packetLimit = 64
|
|
||||||
)
|
|
||||||
|
|
||||||
const planKey = "xray.finalmask.noise.plan"
|
|
||||||
|
|
||||||
type noisePlan struct {
|
|
||||||
packets []scriptPacket
|
|
||||||
bytes int
|
|
||||||
open bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func RegisterLua(L *lua.LState) {
|
|
||||||
L.PreloadModule("xray.finalmask.noise", func(L *lua.LState) int {
|
|
||||||
module := L.CreateTable(0, 2)
|
|
||||||
|
|
||||||
module.RawSetString("Random", L.NewFunction(func(L *lua.LState) int {
|
|
||||||
size := L.CheckNumber(1)
|
|
||||||
if !(size >= 0 && size <= valueLimit) {
|
|
||||||
L.RaiseError("noise.Random requires a size of 0 to %d", valueLimit)
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
buf := make([]byte, int(size))
|
|
||||||
if _, err := rand.Read(buf); err != nil {
|
|
||||||
L.RaiseError("noise.Random failed: %v", err)
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
xlua.PushString(L, string(buf))
|
|
||||||
return 1
|
|
||||||
}))
|
|
||||||
|
|
||||||
module.RawSetString("RandomBetween", L.NewFunction(func(L *lua.LState) int {
|
|
||||||
from, to := L.CheckNumber(1), L.CheckNumber(2)
|
|
||||||
if !(from >= 0 && from <= to && to <= valueLimit) {
|
|
||||||
L.RaiseError("noise.RandomBetween requires 0 <= from <= to <= %d", valueLimit)
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
xlua.PushNumber(L, crypto.RandBetween(int64(from), int64(to)+1))
|
|
||||||
return 1
|
|
||||||
}))
|
|
||||||
|
|
||||||
L.Push(module)
|
|
||||||
return 1
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func registerPlan(L *lua.LState) {
|
|
||||||
meta := L.CreateTable(0, 3)
|
|
||||||
meta.RawSetString("__index", meta)
|
|
||||||
meta.RawSetString("Add", L.NewFunction(planAdd))
|
|
||||||
meta.RawSetString("AddRandom", L.NewFunction(planAddRandom))
|
|
||||||
|
|
||||||
data := L.NewUserData()
|
|
||||||
data.Value = &noisePlan{}
|
|
||||||
data.Metatable = meta
|
|
||||||
L.SetField(L.G.Registry, planKey, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func planAdd(L *lua.LState) int {
|
|
||||||
packet, ok := L.Get(2).(lua.LString)
|
|
||||||
if !ok {
|
|
||||||
L.RaiseError("Add requires a packet string")
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
plan := planFor(L, len(packet))
|
|
||||||
plan.packets = append(plan.packets, scriptPacket{packet: []byte(packet), delay: planDelay(L, 3)})
|
|
||||||
plan.bytes += len(packet)
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func planAddRandom(L *lua.LState) int {
|
|
||||||
size := L.CheckNumber(2)
|
|
||||||
if !(size >= 0 && size <= valueLimit) {
|
|
||||||
L.RaiseError("AddRandom requires a size of 0 to %d", valueLimit)
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
plan := planFor(L, int(size))
|
|
||||||
packet := make([]byte, int(size))
|
|
||||||
if _, err := rand.Read(packet); err != nil {
|
|
||||||
L.RaiseError("AddRandom failed: %v", err)
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
plan.packets = append(plan.packets, scriptPacket{packet: packet, delay: planDelay(L, 3)})
|
|
||||||
plan.bytes += int(size)
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func planFor(L *lua.LState, size int) *noisePlan {
|
|
||||||
plan, err := xlua.ReadUserData[*noisePlan](L.Get(1), "the plan HandleNoise was given is missing")
|
|
||||||
if err != nil {
|
|
||||||
L.RaiseError("%s", err.Error())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if !plan.open {
|
|
||||||
L.RaiseError("the plan only takes packets while HandleNoise runs")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if len(plan.packets) >= packetLimit {
|
|
||||||
L.RaiseError("the plan takes at most %d packets per call", packetLimit)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if plan.bytes+size > valueLimit {
|
|
||||||
L.RaiseError("the plan takes at most %d bytes of noise per call", valueLimit)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return plan
|
|
||||||
}
|
|
||||||
|
|
||||||
func planDelay(L *lua.LState, index int) time.Duration {
|
|
||||||
switch value := L.Get(index).(type) {
|
|
||||||
case *lua.LNilType:
|
|
||||||
return 0
|
|
||||||
case lua.LNumber:
|
|
||||||
if !(value >= 0 && value <= valueLimit) {
|
|
||||||
L.RaiseError("the delay must be 0 to %d milliseconds", valueLimit)
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return time.Duration(value) * time.Millisecond
|
|
||||||
default:
|
|
||||||
L.RaiseError("the delay must be a number of milliseconds")
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func luaPlan(L *lua.LState) (*lua.LUserData, *noisePlan, error) {
|
|
||||||
data, ok := L.GetField(L.G.Registry, planKey).(*lua.LUserData)
|
|
||||||
if !ok {
|
|
||||||
return nil, nil, errors.New("noise script has no plan")
|
|
||||||
}
|
|
||||||
plan, ok := data.Value.(*noisePlan)
|
|
||||||
if !ok {
|
|
||||||
return nil, nil, errors.New("noise script has no plan")
|
|
||||||
}
|
|
||||||
return data, plan, nil
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
package noise
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
func benchmarkEngine(b *testing.B, script string) *scriptEngine {
|
|
||||||
b.Helper()
|
|
||||||
engine, err := newScriptEngine(writeScript(b, script))
|
|
||||||
if err != nil {
|
|
||||||
b.Fatal(err)
|
|
||||||
}
|
|
||||||
b.Cleanup(engine.pool.Close)
|
|
||||||
return engine
|
|
||||||
}
|
|
||||||
|
|
||||||
func benchmarkPackets(b *testing.B, engine *scriptEngine, want int) {
|
|
||||||
b.Helper()
|
|
||||||
b.ReportAllocs()
|
|
||||||
var buf []scriptPacket
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
packets, err := engine.packets("192.0.2.1:443", buf[:0])
|
|
||||||
if err != nil {
|
|
||||||
b.Fatal(err)
|
|
||||||
}
|
|
||||||
if len(packets) != want {
|
|
||||||
b.Fatalf("got %d packets, want %d", len(packets), want)
|
|
||||||
}
|
|
||||||
buf = packets
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkLuaNoiseHook(b *testing.B) {
|
|
||||||
benchmarkPackets(b, benchmarkEngine(b, `function HandleNoise(plan, address) end`), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkLuaNoisePackets(b *testing.B) {
|
|
||||||
b.Run("add", func(b *testing.B) {
|
|
||||||
benchmarkPackets(b, benchmarkEngine(b, `
|
|
||||||
local noise = require("xray.finalmask.noise")
|
|
||||||
|
|
||||||
function HandleNoise(plan, address)
|
|
||||||
plan:Add(noise.Random(64))
|
|
||||||
plan:Add(noise.Random(64), 0)
|
|
||||||
end
|
|
||||||
`), 2)
|
|
||||||
})
|
|
||||||
|
|
||||||
b.Run("addrandom", func(b *testing.B) {
|
|
||||||
benchmarkPackets(b, benchmarkEngine(b, `
|
|
||||||
function HandleNoise(plan, address)
|
|
||||||
plan:AddRandom(64)
|
|
||||||
plan:AddRandom(64, 0)
|
|
||||||
end
|
|
||||||
`), 2)
|
|
||||||
})
|
|
||||||
|
|
||||||
b.Run("static", func(b *testing.B) {
|
|
||||||
conn := &noiseConn{config: &Config{Items: []*Item{
|
|
||||||
{RandMin: 64, RandMax: 64, RandRangeMax: 255},
|
|
||||||
{RandMin: 64, RandMax: 64, RandRangeMax: 255},
|
|
||||||
}}}
|
|
||||||
b.ReportAllocs()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
for _, item := range conn.config.Items {
|
|
||||||
if len(conn.buildPacket(item)) != 64 {
|
|
||||||
b.Fatal("unexpected packet size")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
package noise
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
|
||||||
"github.com/xtls/xray-core/common/log"
|
|
||||||
xlua "github.com/xtls/xray-core/common/lua"
|
|
||||||
lua "github.com/yuin/gopher-lua"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
scriptExecutionTimeout = time.Second
|
|
||||||
scriptInitTimeout = 120 * time.Second
|
|
||||||
)
|
|
||||||
|
|
||||||
type scriptEngine struct {
|
|
||||||
pool *xlua.Pool
|
|
||||||
}
|
|
||||||
|
|
||||||
type scriptPacket struct {
|
|
||||||
packet []byte
|
|
||||||
delay time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: close the pool of a mask whose inbound or outbound the API removed.
|
|
||||||
var (
|
|
||||||
scriptsMu sync.Mutex
|
|
||||||
scripts = make(map[*Config]*scriptEngine)
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *Config) engine() (*scriptEngine, error) {
|
|
||||||
scriptsMu.Lock()
|
|
||||||
defer scriptsMu.Unlock()
|
|
||||||
|
|
||||||
if engine := scripts[c]; engine != nil {
|
|
||||||
return engine, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
engine, err := newScriptEngine(c.Script)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
scripts[c] = engine
|
|
||||||
return engine, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newScriptEngine(script string) (*scriptEngine, error) {
|
|
||||||
program, err := xlua.Compile(script)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pool, err := xlua.NewPool(context.Background(), scriptExecutionTimeout, program.NewStateFactory(
|
|
||||||
scriptInitTimeout,
|
|
||||||
256,
|
|
||||||
func(L *lua.LState) {
|
|
||||||
log.RegisterLua(L)
|
|
||||||
RegisterLua(L)
|
|
||||||
registerPlan(L)
|
|
||||||
},
|
|
||||||
func(L *lua.LState) error {
|
|
||||||
if L.GetGlobal("HandleNoise").Type() != lua.LTFunction {
|
|
||||||
return errors.New("noise script must define HandleNoise(...)")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &scriptEngine{pool: pool}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *scriptEngine) packets(addr string, into []scriptPacket) ([]scriptPacket, error) {
|
|
||||||
var noise []scriptPacket
|
|
||||||
var reported error
|
|
||||||
if err := e.pool.WithState(nil, 0, func(L *lua.LState) error {
|
|
||||||
var err error
|
|
||||||
noise, reported, err = callHook(L, addr, into)
|
|
||||||
return err
|
|
||||||
}); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return noise, reported
|
|
||||||
}
|
|
||||||
|
|
||||||
func callHook(L *lua.LState, addr string, into []scriptPacket) (packets []scriptPacket, reported error, err error) {
|
|
||||||
data, plan, err := luaPlan(L)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
hook := L.GetGlobal("HandleNoise")
|
|
||||||
if hook.Type() != lua.LTFunction {
|
|
||||||
return nil, nil, errors.New("noise script must define HandleNoise(...)")
|
|
||||||
}
|
|
||||||
|
|
||||||
plan.packets, plan.bytes, plan.open = into[:0], 0, true
|
|
||||||
defer func() { plan.packets, plan.open = nil, false }()
|
|
||||||
|
|
||||||
if err := L.CallByParam(lua.P{Fn: hook, NRet: 1, Protect: true}, data, lua.LString(addr)); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return plan.packets, xlua.ReadError(L.Get(-1), "noise script error must be an error or a string"), nil
|
|
||||||
}
|
|
||||||
@@ -1,725 +0,0 @@
|
|||||||
package noise
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"sync"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
xlua "github.com/xtls/xray-core/common/lua"
|
|
||||||
lua "github.com/yuin/gopher-lua"
|
|
||||||
)
|
|
||||||
|
|
||||||
func newScriptConn(t *testing.T, script string) (*noiseConn, *fakePacketConn) {
|
|
||||||
t.Helper()
|
|
||||||
raw := &fakePacketConn{}
|
|
||||||
conn, err := NewConnClient(&Config{Script: writeScript(t, script)}, raw)
|
|
||||||
require.NoError(t, err)
|
|
||||||
return conn.(*noiseConn), raw
|
|
||||||
}
|
|
||||||
|
|
||||||
func newScriptConnError(t *testing.T, script string) error {
|
|
||||||
t.Helper()
|
|
||||||
_, err := NewConnClient(&Config{Script: writeScript(t, script)}, &fakePacketConn{})
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestScriptSendsStrings(t *testing.T) {
|
|
||||||
conn, raw := newScriptConn(t, `
|
|
||||||
local noise = require("xray.finalmask.noise")
|
|
||||||
|
|
||||||
function HandleNoise(plan, address)
|
|
||||||
plan:Add("first")
|
|
||||||
plan:Add("second")
|
|
||||||
end
|
|
||||||
`)
|
|
||||||
n, err := conn.WriteTo([]byte("payload"), &net.UDPAddr{IP: net.IPv4(1, 2, 3, 4), Port: 443})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, 7, n)
|
|
||||||
require.Equal(t, [][]byte{[]byte("first"), []byte("second"), []byte("payload")}, raw.packets())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestScriptSendsTables(t *testing.T) {
|
|
||||||
conn, raw := newScriptConn(t, `
|
|
||||||
local noise = require("xray.finalmask.noise")
|
|
||||||
|
|
||||||
function HandleNoise(plan, address)
|
|
||||||
plan:Add("a")
|
|
||||||
plan:Add("bb", 0)
|
|
||||||
end
|
|
||||||
`)
|
|
||||||
_, err := conn.WriteTo([]byte("p"), &net.UDPAddr{Port: 1})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, [][]byte{[]byte("a"), []byte("bb"), []byte("p")}, raw.packets())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestScriptDelaysPackets(t *testing.T) {
|
|
||||||
conn, raw := newScriptConn(t, `
|
|
||||||
local noise = require("xray.finalmask.noise")
|
|
||||||
|
|
||||||
function HandleNoise(plan, address)
|
|
||||||
plan:Add("x", 60)
|
|
||||||
end
|
|
||||||
`)
|
|
||||||
start := time.Now()
|
|
||||||
_, err := conn.WriteTo([]byte("p"), &net.UDPAddr{Port: 1})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.GreaterOrEqual(t, time.Since(start), 60*time.Millisecond)
|
|
||||||
require.Equal(t, [][]byte{[]byte("x"), []byte("p")}, raw.packets())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestScriptReceivesAddress(t *testing.T) {
|
|
||||||
conn, raw := newScriptConn(t, `
|
|
||||||
local noise = require("xray.finalmask.noise")
|
|
||||||
|
|
||||||
function HandleNoise(plan, address)
|
|
||||||
plan:Add(address)
|
|
||||||
end
|
|
||||||
`)
|
|
||||||
addr := &net.UDPAddr{IP: net.IPv4(203, 0, 113, 7), Port: 8443}
|
|
||||||
_, err := conn.WriteTo([]byte("p"), addr)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, [][]byte{[]byte(addr.String()), []byte("p")}, raw.packets())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestScriptSendsNoNoise(t *testing.T) {
|
|
||||||
for name, script := range map[string]string{
|
|
||||||
"sends nothing": "function HandleNoise(plan, address) end",
|
|
||||||
"returns nil": "function HandleNoise(plan, address) return nil end",
|
|
||||||
"returns a zero": "function HandleNoise(plan, address) return 0 end",
|
|
||||||
} {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
conn, raw := newScriptConn(t, script)
|
|
||||||
_, err := conn.WriteTo([]byte("p"), &net.UDPAddr{Port: 1})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, [][]byte{[]byte("p")}, raw.packets())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestScriptRandom(t *testing.T) {
|
|
||||||
conn, raw := newScriptConn(t, `
|
|
||||||
local noise = require("xray.finalmask.noise")
|
|
||||||
|
|
||||||
function HandleNoise(plan, address)
|
|
||||||
local size = noise.RandomBetween(20, 20)
|
|
||||||
plan:Add(noise.Random(size))
|
|
||||||
plan:Add(noise.Random(0))
|
|
||||||
end
|
|
||||||
`)
|
|
||||||
_, err := conn.WriteTo([]byte("p"), &net.UDPAddr{Port: 1})
|
|
||||||
require.NoError(t, err)
|
|
||||||
packets := raw.packets()
|
|
||||||
require.Len(t, packets, 3)
|
|
||||||
require.Len(t, packets[0], 20)
|
|
||||||
require.Empty(t, packets[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestScriptRandomBetweenStaysInRange(t *testing.T) {
|
|
||||||
conn, raw := newScriptConn(t, `
|
|
||||||
local noise = require("xray.finalmask.noise")
|
|
||||||
|
|
||||||
function HandleNoise(plan, address)
|
|
||||||
for i = 1, 64 do
|
|
||||||
plan:Add(string.rep("x", noise.RandomBetween(1, 3)))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
`)
|
|
||||||
_, err := conn.WriteTo([]byte("p"), &net.UDPAddr{Port: 1})
|
|
||||||
require.NoError(t, err)
|
|
||||||
packets := raw.packets()
|
|
||||||
require.Len(t, packets, 65)
|
|
||||||
sizes := make(map[int]bool)
|
|
||||||
for _, packet := range packets[:64] {
|
|
||||||
require.GreaterOrEqual(t, len(packet), 1)
|
|
||||||
require.LessOrEqual(t, len(packet), 3)
|
|
||||||
sizes[len(packet)] = true
|
|
||||||
}
|
|
||||||
require.Len(t, sizes, 3, "every size in the range should show up")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestScriptKeepsStateBetweenPeers(t *testing.T) {
|
|
||||||
conn, raw := newScriptConn(t, `
|
|
||||||
local noise = require("xray.finalmask.noise")
|
|
||||||
local sent = 0
|
|
||||||
|
|
||||||
function HandleNoise(plan, address)
|
|
||||||
sent = sent + 1
|
|
||||||
plan:Add(string.rep("x", sent))
|
|
||||||
end
|
|
||||||
`)
|
|
||||||
for i := 1; i <= 3; i++ {
|
|
||||||
_, err := conn.WriteTo([]byte("p"), &net.UDPAddr{Port: i})
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
require.Equal(t, [][]byte{
|
|
||||||
[]byte("x"), []byte("p"),
|
|
||||||
[]byte("xx"), []byte("p"),
|
|
||||||
[]byte("xxx"), []byte("p"),
|
|
||||||
}, raw.packets())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestScriptRunsOncePerPeerUntilReset(t *testing.T) {
|
|
||||||
script := `local noise = require("xray.finalmask.noise")
|
|
||||||
function HandleNoise(plan, address) plan:Add("n") end`
|
|
||||||
raw := &fakePacketConn{}
|
|
||||||
conn, err := NewConnClient(&Config{Script: writeScript(t, script), ResetMin: 0, ResetMax: 0}, raw)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
addr := &net.UDPAddr{Port: 1}
|
|
||||||
for i := 0; i < 3; i++ {
|
|
||||||
_, err := conn.WriteTo([]byte("p"), addr)
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
require.Equal(t, [][]byte{[]byte("n"), []byte("p"), []byte("p"), []byte("p")}, raw.packets())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestScriptRunsAgainAfterReset(t *testing.T) {
|
|
||||||
script := `local noise = require("xray.finalmask.noise")
|
|
||||||
function HandleNoise(plan, address) plan:Add("n") end`
|
|
||||||
raw := &fakePacketConn{}
|
|
||||||
conn, err := NewConnClient(&Config{Script: writeScript(t, script), ResetMin: 0, ResetMax: 1}, raw)
|
|
||||||
require.NoError(t, err)
|
|
||||||
noiseConn := conn.(*noiseConn)
|
|
||||||
|
|
||||||
addr := &net.UDPAddr{Port: 1}
|
|
||||||
_, err = noiseConn.WriteTo([]byte("p"), addr)
|
|
||||||
require.NoError(t, err)
|
|
||||||
noiseConn.m[addr.String()] = time.Now().Add(-time.Second)
|
|
||||||
_, err = noiseConn.WriteTo([]byte("p"), addr)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, [][]byte{[]byte("n"), []byte("p"), []byte("n"), []byte("p")}, raw.packets())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestScriptFailureStillSendsPayload(t *testing.T) {
|
|
||||||
for name, script := range map[string]string{
|
|
||||||
"raises": `function HandleNoise(plan, address) error("boom") end`,
|
|
||||||
"reports an error": `function HandleNoise(plan, address) return "nope" end`,
|
|
||||||
"packet is a bool": `local noise = require("xray.finalmask.noise")
|
|
||||||
function HandleNoise(plan, address) plan:Add(true) end`,
|
|
||||||
"packet is a number": `local noise = require("xray.finalmask.noise")
|
|
||||||
function HandleNoise(plan, address) plan:Add(5) end`,
|
|
||||||
"no packet": `local noise = require("xray.finalmask.noise")
|
|
||||||
function HandleNoise(plan, address) plan:Add() end`,
|
|
||||||
"delay is negative": `local noise = require("xray.finalmask.noise")
|
|
||||||
function HandleNoise(plan, address) plan:Add("x", -1) end`,
|
|
||||||
"delay is too big": `local noise = require("xray.finalmask.noise")
|
|
||||||
function HandleNoise(plan, address) plan:Add("x", 65536) end`,
|
|
||||||
"delay is not a number": `local noise = require("xray.finalmask.noise")
|
|
||||||
function HandleNoise(plan, address) plan:Add("x", "soon") end`,
|
|
||||||
} {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
conn, raw := newScriptConn(t, script)
|
|
||||||
n, err := conn.WriteTo([]byte("payload"), &net.UDPAddr{Port: 1})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, 7, n)
|
|
||||||
require.Equal(t, [][]byte{[]byte("payload")}, raw.packets())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestScriptFailsToLoad(t *testing.T) {
|
|
||||||
for name, script := range map[string]string{
|
|
||||||
"no HandleNoise": `function Other() end`,
|
|
||||||
"HandleNoise is data": `HandleNoise = 1`,
|
|
||||||
"syntax error": `function HandleNoise(`,
|
|
||||||
"top level error": `error("boom")`,
|
|
||||||
} {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
require.Error(t, newScriptConnError(t, script))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestScriptNameInErrors(t *testing.T) {
|
|
||||||
err := newScriptConnError(t, `function HandleNoise(`)
|
|
||||||
require.Error(t, err)
|
|
||||||
require.Contains(t, err.Error(), "noise.lua", "errors name the file the script came from")
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeScript(t testing.TB, script string) string {
|
|
||||||
t.Helper()
|
|
||||||
path := filepath.Join(t.TempDir(), "noise.lua")
|
|
||||||
require.NoError(t, os.WriteFile(path, []byte(script), 0o600))
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestScriptFromAFile(t *testing.T) {
|
|
||||||
raw := &fakePacketConn{}
|
|
||||||
conn, err := NewConnClient(&Config{Script: writeScript(t, `
|
|
||||||
local noise = require("xray.finalmask.noise")
|
|
||||||
|
|
||||||
function HandleNoise(plan, address)
|
|
||||||
plan:Add("from a file")
|
|
||||||
end
|
|
||||||
`)}, raw)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
_, err = conn.WriteTo([]byte("payload"), &net.UDPAddr{IP: net.IPv4(1, 2, 3, 4), Port: 443})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, [][]byte{[]byte("from a file"), []byte("payload")}, raw.packets())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestScriptFileIsMissing(t *testing.T) {
|
|
||||||
_, err := NewConnClient(&Config{Script: filepath.Join(t.TempDir(), "absent.lua")}, &fakePacketConn{})
|
|
||||||
require.Error(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConnReusesTheEngine(t *testing.T) {
|
|
||||||
script := `local noise = require("xray.finalmask.noise")
|
|
||||||
function HandleNoise(plan, address) plan:Add("n") end`
|
|
||||||
|
|
||||||
config := &Config{Script: writeScript(t, script)}
|
|
||||||
first, err := NewConnClient(config, &fakePacketConn{})
|
|
||||||
require.NoError(t, err)
|
|
||||||
second, err := NewConnClient(config, &fakePacketConn{})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Same(t, first.(*noiseConn).script, second.(*noiseConn).script, "two connections share one pool")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestScriptIsSharedPerConfig(t *testing.T) {
|
|
||||||
script := `local noise = require("xray.finalmask.noise")
|
|
||||||
function HandleNoise(plan, address) plan:Add("n") end`
|
|
||||||
|
|
||||||
path := writeScript(t, script)
|
|
||||||
config := &Config{Script: path}
|
|
||||||
first, err := NewConnClient(config, &fakePacketConn{})
|
|
||||||
require.NoError(t, err)
|
|
||||||
second, err := NewConnClient(config, &fakePacketConn{})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Same(t, first.(*noiseConn).script, second.(*noiseConn).script,
|
|
||||||
"the same config reuses its pool")
|
|
||||||
|
|
||||||
other, err := NewConnClient(&Config{Script: path}, &fakePacketConn{})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotSame(t, first.(*noiseConn).script, other.(*noiseConn).script,
|
|
||||||
"another config naming the same file gets its own pool")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestScriptStateIsNotSharedBetweenConfigs(t *testing.T) {
|
|
||||||
script := `
|
|
||||||
local noise = require("xray.finalmask.noise")
|
|
||||||
local calls = 0
|
|
||||||
|
|
||||||
function HandleNoise(plan, address)
|
|
||||||
calls = calls + 1
|
|
||||||
plan:Add(string.rep("x", calls))
|
|
||||||
end
|
|
||||||
`
|
|
||||||
path := writeScript(t, script)
|
|
||||||
for i := 0; i < 2; i++ {
|
|
||||||
raw := &fakePacketConn{}
|
|
||||||
conn, err := NewConnClient(&Config{Script: path}, raw)
|
|
||||||
require.NoError(t, err)
|
|
||||||
_, err = conn.WriteTo([]byte("p"), &net.UDPAddr{Port: 1})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, [][]byte{[]byte("x"), []byte("p")}, raw.packets())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestScriptReportsAnError(t *testing.T) {
|
|
||||||
for name, script := range map[string]string{
|
|
||||||
"after sending": `local noise = require("xray.finalmask.noise")
|
|
||||||
function HandleNoise(plan, address)
|
|
||||||
plan:Add("n")
|
|
||||||
return "not today"
|
|
||||||
end`,
|
|
||||||
"without sending": `function HandleNoise(plan, address) return "not today" end`,
|
|
||||||
} {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
conn, raw := newScriptConn(t, script)
|
|
||||||
_, err := conn.WriteTo([]byte("p"), &net.UDPAddr{Port: 1})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, [][]byte{[]byte("p")}, raw.packets())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestScriptInstanceSurvivesABadReturn(t *testing.T) {
|
|
||||||
conn, raw := newScriptConn(t, `
|
|
||||||
local noise = require("xray.finalmask.noise")
|
|
||||||
local calls = 0
|
|
||||||
|
|
||||||
function HandleNoise(plan, address)
|
|
||||||
calls = calls + 1
|
|
||||||
if calls == 1 then
|
|
||||||
return "first call refused"
|
|
||||||
end
|
|
||||||
plan:Add("call-" .. calls)
|
|
||||||
end
|
|
||||||
`)
|
|
||||||
for i := 1; i <= 2; i++ {
|
|
||||||
_, err := conn.WriteTo([]byte("p"), &net.UDPAddr{Port: i})
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
require.Equal(t, [][]byte{[]byte("p"), []byte("call-2"), []byte("p")}, raw.packets(),
|
|
||||||
"a refused return value keeps the instance, so its counter survives")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestScriptInstanceIsDroppedAfterAnException(t *testing.T) {
|
|
||||||
conn, raw := newScriptConn(t, `
|
|
||||||
local noise = require("xray.finalmask.noise")
|
|
||||||
local calls = 0
|
|
||||||
|
|
||||||
function HandleNoise(plan, address)
|
|
||||||
calls = calls + 1
|
|
||||||
if calls == 1 then
|
|
||||||
error("boom")
|
|
||||||
end
|
|
||||||
plan:Add("call-" .. calls)
|
|
||||||
end
|
|
||||||
`)
|
|
||||||
for i := 1; i <= 2; i++ {
|
|
||||||
_, err := conn.WriteTo([]byte("p"), &net.UDPAddr{Port: i})
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
require.Equal(t, [][]byte{[]byte("p"), []byte("p")}, raw.packets(),
|
|
||||||
"an exception destroys the instance, so the next call gets a fresh counter and raises again")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestScriptCanLog(t *testing.T) {
|
|
||||||
conn, raw := newScriptConn(t, `
|
|
||||||
local log = require("xray.log")
|
|
||||||
local noise = require("xray.finalmask.noise")
|
|
||||||
|
|
||||||
function HandleNoise(plan, address)
|
|
||||||
log.Debug("noise for ", address)
|
|
||||||
plan:Add("n")
|
|
||||||
end
|
|
||||||
`)
|
|
||||||
_, err := conn.WriteTo([]byte("p"), &net.UDPAddr{Port: 1})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, [][]byte{[]byte("n"), []byte("p")}, raw.packets())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestScriptRandomRejectsBadArguments(t *testing.T) {
|
|
||||||
for name, script := range map[string]string{
|
|
||||||
"negative size": `function HandleNoise(plan, address) require("xray.finalmask.noise").Random(-1) end`,
|
|
||||||
"reversed range": `function HandleNoise(plan, address) require("xray.finalmask.noise").RandomBetween(5, 1) end`,
|
|
||||||
} {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
conn, raw := newScriptConn(t, script)
|
|
||||||
_, err := conn.WriteTo([]byte("p"), &net.UDPAddr{Port: 1})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, [][]byte{[]byte("p")}, raw.packets())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestScriptStaticNoiseStillWorks(t *testing.T) {
|
|
||||||
raw := &fakePacketConn{}
|
|
||||||
conn, err := NewConnClient(&Config{Items: []*Item{{Packet: []byte("static")}}}, raw)
|
|
||||||
require.NoError(t, err)
|
|
||||||
_, err = conn.WriteTo([]byte("p"), &net.UDPAddr{Port: 1})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, [][]byte{[]byte("static"), []byte("p")}, raw.packets())
|
|
||||||
require.Nil(t, conn.(*noiseConn).script)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestScriptSendsInOrder(t *testing.T) {
|
|
||||||
conn, raw := newScriptConn(t, `
|
|
||||||
local noise = require("xray.finalmask.noise")
|
|
||||||
|
|
||||||
function HandleNoise(plan, address)
|
|
||||||
plan:Add("first")
|
|
||||||
plan:Add("second")
|
|
||||||
plan:Add("third")
|
|
||||||
end
|
|
||||||
`)
|
|
||||||
_, err := conn.WriteTo([]byte("p"), &net.UDPAddr{Port: 1})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, [][]byte{[]byte("first"), []byte("second"), []byte("third"), []byte("p")}, raw.packets())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestScriptBinaryPacket(t *testing.T) {
|
|
||||||
conn, raw := newScriptConn(t, `
|
|
||||||
local noise = require("xray.finalmask.noise")
|
|
||||||
|
|
||||||
function HandleNoise(plan, address)
|
|
||||||
plan:Add("\0\1\255")
|
|
||||||
end
|
|
||||||
`)
|
|
||||||
_, err := conn.WriteTo([]byte("p"), &net.UDPAddr{Port: 1})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, [][]byte{{0x00, 0x01, 0xff}, []byte("p")}, raw.packets())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestScriptOnServerConn(t *testing.T) {
|
|
||||||
script := `local noise = require("xray.finalmask.noise")
|
|
||||||
function HandleNoise(plan, address) plan:Add("n") end`
|
|
||||||
raw := &fakePacketConn{}
|
|
||||||
conn, err := NewConnServer(&Config{Script: writeScript(t, script)}, raw)
|
|
||||||
require.NoError(t, err)
|
|
||||||
_, err = conn.WriteTo([]byte("p"), &net.UDPAddr{Port: 1})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, [][]byte{[]byte("n"), []byte("p")}, raw.packets())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestScriptDropsHandleNoise(t *testing.T) {
|
|
||||||
conn, raw := newScriptConn(t, `
|
|
||||||
local noise = require("xray.finalmask.noise")
|
|
||||||
|
|
||||||
function HandleNoise(plan, address)
|
|
||||||
HandleNoise = nil
|
|
||||||
plan:Add("once")
|
|
||||||
end
|
|
||||||
`)
|
|
||||||
for i := 1; i <= 2; i++ {
|
|
||||||
_, err := conn.WriteTo([]byte("p"), &net.UDPAddr{Port: i})
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
require.Equal(t, [][]byte{[]byte("once"), []byte("p"), []byte("p")}, raw.packets(),
|
|
||||||
"a state that lost HandleNoise sends no noise instead of failing the write")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestScriptValueLimits(t *testing.T) {
|
|
||||||
for name, script := range map[string]string{
|
|
||||||
"random too big": `function HandleNoise(plan, address) require("xray.finalmask.noise").Random(65536) end`,
|
|
||||||
"random bound too big": `function HandleNoise(plan, address) require("xray.finalmask.noise").RandomBetween(0, 65536) end`,
|
|
||||||
"random bound negative": `function HandleNoise(plan, address) require("xray.finalmask.noise").RandomBetween(-1, 1) end`,
|
|
||||||
"delay too big": `local noise = require("xray.finalmask.noise")
|
|
||||||
function HandleNoise(plan, address) plan:Add("x", 65536) end`,
|
|
||||||
"huge delay from a float": `local noise = require("xray.finalmask.noise")
|
|
||||||
function HandleNoise(plan, address) plan:Add("x", 1e18) end`,
|
|
||||||
"delay is not a number": `local noise = require("xray.finalmask.noise")
|
|
||||||
function HandleNoise(plan, address) plan:Add("x", 0 / 0) end`,
|
|
||||||
"random size is not a number": `function HandleNoise(plan, address) require("xray.finalmask.noise").Random(0 / 0) end`,
|
|
||||||
} {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
conn, raw := newScriptConn(t, script)
|
|
||||||
_, err := conn.WriteTo([]byte("p"), &net.UDPAddr{Port: 1})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, [][]byte{[]byte("p")}, raw.packets())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestScriptValueLimitsAccepted(t *testing.T) {
|
|
||||||
conn, raw := newScriptConn(t, `
|
|
||||||
local noise = require("xray.finalmask.noise")
|
|
||||||
|
|
||||||
function HandleNoise(plan, address)
|
|
||||||
plan:Add(noise.Random(65524))
|
|
||||||
plan:Add("x", 0)
|
|
||||||
plan:Add(tostring(noise.RandomBetween(65535, 65535)))
|
|
||||||
end
|
|
||||||
`)
|
|
||||||
_, err := conn.WriteTo([]byte("p"), &net.UDPAddr{Port: 1})
|
|
||||||
require.NoError(t, err)
|
|
||||||
packets := raw.packets()
|
|
||||||
require.Len(t, packets, 4)
|
|
||||||
require.Len(t, packets[0], 65524, "the plan's whole budget minus the two packets after it")
|
|
||||||
require.Equal(t, []byte("65535"), packets[2])
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestScriptSendsManyPackets(t *testing.T) {
|
|
||||||
conn, raw := newScriptConn(t, `
|
|
||||||
local noise = require("xray.finalmask.noise")
|
|
||||||
|
|
||||||
function HandleNoise(plan, address)
|
|
||||||
for i = 1, 64 do
|
|
||||||
plan:Add("n")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
`)
|
|
||||||
_, err := conn.WriteTo([]byte("p"), &net.UDPAddr{Port: 1})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Len(t, raw.packets(), 65)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestScriptPlanLimits(t *testing.T) {
|
|
||||||
for name, script := range map[string]string{
|
|
||||||
"too many packets": `function HandleNoise(plan, address)
|
|
||||||
for i = 1, 65 do
|
|
||||||
plan:Add("n")
|
|
||||||
end
|
|
||||||
end`,
|
|
||||||
"too many bytes": `local noise = require("xray.finalmask.noise")
|
|
||||||
function HandleNoise(plan, address)
|
|
||||||
for i = 1, 2 do
|
|
||||||
plan:Add(noise.Random(40000))
|
|
||||||
end
|
|
||||||
end`,
|
|
||||||
"a shared config cannot fill memory": `local noise = require("xray.finalmask.noise")
|
|
||||||
function HandleNoise(plan, address)
|
|
||||||
while true do
|
|
||||||
plan:Add(noise.Random(65535))
|
|
||||||
end
|
|
||||||
end`,
|
|
||||||
} {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
conn, raw := newScriptConn(t, script)
|
|
||||||
_, err := conn.WriteTo([]byte("p"), &net.UDPAddr{Port: 1})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, [][]byte{[]byte("p")}, raw.packets(), "the noise is dropped and the payload still goes out")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestScriptKeepsPacketsSentBeforeAnError(t *testing.T) {
|
|
||||||
conn, raw := newScriptConn(t, `
|
|
||||||
local noise = require("xray.finalmask.noise")
|
|
||||||
|
|
||||||
function HandleNoise(plan, address)
|
|
||||||
plan:Add("sent")
|
|
||||||
error("boom")
|
|
||||||
end
|
|
||||||
`)
|
|
||||||
_, err := conn.WriteTo([]byte("p"), &net.UDPAddr{Port: 1})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, [][]byte{[]byte("p")}, raw.packets(),
|
|
||||||
"an exception drops everything the call had queued")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPlanAddRandom(t *testing.T) {
|
|
||||||
conn, raw := newScriptConn(t, `
|
|
||||||
local noise = require("xray.finalmask.noise")
|
|
||||||
|
|
||||||
function HandleNoise(plan, address)
|
|
||||||
plan:AddRandom(40)
|
|
||||||
plan:AddRandom(noise.RandomBetween(16, 16), 1)
|
|
||||||
end
|
|
||||||
`)
|
|
||||||
_, err := conn.WriteTo([]byte("p"), &net.UDPAddr{Port: 1})
|
|
||||||
require.NoError(t, err)
|
|
||||||
packets := raw.packets()
|
|
||||||
require.Len(t, packets, 3)
|
|
||||||
require.Len(t, packets[0], 40)
|
|
||||||
require.Len(t, packets[1], 16)
|
|
||||||
require.NotEqual(t, packets[0][:16], packets[1], "every packet gets its own random bytes")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPlanAddRandomLimits(t *testing.T) {
|
|
||||||
for name, script := range map[string]string{
|
|
||||||
"size too big": `function HandleNoise(plan, address) plan:AddRandom(65536) end`,
|
|
||||||
"negative size": `function HandleNoise(plan, address) plan:AddRandom(-1) end`,
|
|
||||||
"size is not a number": `function HandleNoise(plan, address) plan:AddRandom(0 / 0) end`,
|
|
||||||
"over the budget": `function HandleNoise(plan, address)
|
|
||||||
plan:AddRandom(40000)
|
|
||||||
plan:AddRandom(40000)
|
|
||||||
end`,
|
|
||||||
"bad delay": `function HandleNoise(plan, address) plan:AddRandom(8, -1) end`,
|
|
||||||
} {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
conn, raw := newScriptConn(t, script)
|
|
||||||
_, err := conn.WriteTo([]byte("p"), &net.UDPAddr{Port: 1})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, [][]byte{[]byte("p")}, raw.packets())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPlanAddNeedsThePlan(t *testing.T) {
|
|
||||||
conn, raw := newScriptConn(t, `
|
|
||||||
local noise = require("xray.finalmask.noise")
|
|
||||||
|
|
||||||
function HandleNoise(plan, address)
|
|
||||||
local send = plan.Add
|
|
||||||
send("not a plan", "x")
|
|
||||||
end
|
|
||||||
`)
|
|
||||||
_, err := conn.WriteTo([]byte("p"), &net.UDPAddr{Port: 1})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, [][]byte{[]byte("p")}, raw.packets(), "an Add without its plan drops the noise")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPlanTakesNoPacketsOutsideTheHook(t *testing.T) {
|
|
||||||
L := lua.NewState()
|
|
||||||
defer L.Close()
|
|
||||||
RegisterLua(L)
|
|
||||||
registerPlan(L)
|
|
||||||
|
|
||||||
data, _, err := luaPlan(L)
|
|
||||||
require.NoError(t, err)
|
|
||||||
err = L.CallByParam(lua.P{Fn: L.GetField(data.Metatable, "Add"), Protect: true}, data, lua.LString("too early"))
|
|
||||||
require.Error(t, err)
|
|
||||||
require.Contains(t, err.Error(), "while HandleNoise runs")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRegisterLuaHasNoSend(t *testing.T) {
|
|
||||||
L := lua.NewState()
|
|
||||||
defer L.Close()
|
|
||||||
RegisterLua(L)
|
|
||||||
require.NoError(t, L.DoString(`
|
|
||||||
local noise = require("xray.finalmask.noise")
|
|
||||||
if noise.Add ~= nil then
|
|
||||||
error("Add should not be exported to other modules")
|
|
||||||
end
|
|
||||||
assert(#noise.Random(4) == 4)
|
|
||||||
assert(noise.RandomBetween(3, 3) == 3)
|
|
||||||
`))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEngineWithoutAScript(t *testing.T) {
|
|
||||||
_, err := (&Config{}).engine()
|
|
||||||
require.Error(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestScriptUnderConcurrency(t *testing.T) {
|
|
||||||
script := `
|
|
||||||
local noise = require("xray.finalmask.noise")
|
|
||||||
|
|
||||||
function HandleNoise(plan, address)
|
|
||||||
plan:Add(noise.Random(8))
|
|
||||||
plan:Add(address, 1)
|
|
||||||
end
|
|
||||||
`
|
|
||||||
config := &Config{Script: writeScript(t, script)}
|
|
||||||
raw := &fakePacketConn{}
|
|
||||||
conn, err := NewConnClient(config, raw)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
const peers = 40
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
for i := 0; i < peers; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func(port int) {
|
|
||||||
defer wg.Done()
|
|
||||||
_, err := conn.WriteTo([]byte("p"), &net.UDPAddr{Port: port})
|
|
||||||
require.NoError(t, err)
|
|
||||||
}(i + 1)
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
require.Len(t, raw.packets(), peers*3, "every peer gets two noise packets and its payload")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEngineIsCreatedOnceUnderConcurrency(t *testing.T) {
|
|
||||||
script := `local noise = require("xray.finalmask.noise")
|
|
||||||
function HandleNoise(plan, address) plan:Add("n") end`
|
|
||||||
config := &Config{Script: writeScript(t, script)}
|
|
||||||
|
|
||||||
engines := make([]*scriptEngine, 16)
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
for i := range engines {
|
|
||||||
wg.Add(1)
|
|
||||||
go func(i int) {
|
|
||||||
defer wg.Done()
|
|
||||||
engine, err := config.engine()
|
|
||||||
require.NoError(t, err)
|
|
||||||
engines[i] = engine
|
|
||||||
}(i)
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
for _, engine := range engines {
|
|
||||||
require.Same(t, engines[0], engine)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPacketsWithoutAPlan(t *testing.T) {
|
|
||||||
program, err := xlua.Compile(writeScript(t, `function HandleNoise(plan, address) end`))
|
|
||||||
require.NoError(t, err)
|
|
||||||
pool, err := xlua.NewPool(context.Background(), time.Second,
|
|
||||||
program.NewStateFactory(time.Minute, 256, RegisterLua, nil))
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer pool.Close()
|
|
||||||
|
|
||||||
_, err = (&scriptEngine{pool: pool}).packets("192.0.2.1:443", nil)
|
|
||||||
require.Error(t, err)
|
|
||||||
require.Contains(t, err.Error(), "no plan")
|
|
||||||
}
|
|
||||||
@@ -6,21 +6,17 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"io"
|
"io"
|
||||||
gonet "net"
|
gonet "net"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"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/proxy"
|
"github.com/xtls/xray-core/proxy"
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask"
|
"github.com/xtls/xray-core/transport/internet/finalmask"
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
|
"github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask/mkcp/aes128gcm"
|
"github.com/xtls/xray-core/transport/internet/finalmask/mkcp/aes128gcm"
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask/mkcp/header"
|
"github.com/xtls/xray-core/transport/internet/finalmask/mkcp/header"
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask/mkcp/original"
|
"github.com/xtls/xray-core/transport/internet/finalmask/mkcp/original"
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask/noise"
|
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask/salamander"
|
"github.com/xtls/xray-core/transport/internet/finalmask/salamander"
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask/sudoku"
|
"github.com/xtls/xray-core/transport/internet/finalmask/sudoku"
|
||||||
)
|
)
|
||||||
@@ -1047,70 +1043,3 @@ func TestSudokuBDD(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNoiseScriptOverRealSockets(t *testing.T) {
|
|
||||||
path := filepath.Join(t.TempDir(), "noise.lua")
|
|
||||||
script := `
|
|
||||||
local noise = require("xray.finalmask.noise")
|
|
||||||
|
|
||||||
function HandleNoise(plan, address)
|
|
||||||
plan:Add("\22\3\1")
|
|
||||||
plan:Add(noise.Random(noise.RandomBetween(16, 16)), 1)
|
|
||||||
end
|
|
||||||
`
|
|
||||||
if err := os.WriteFile(path, []byte(script), 0o600); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
mask, err := (&conf.NoiseMask{Script: path}).Build()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
clientRaw, err := gonet.ListenPacket("udp", "127.0.0.1:0")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
t.Cleanup(func() { _ = clientRaw.Close() })
|
|
||||||
serverRaw, err := gonet.ListenPacket("udp", "127.0.0.1:0")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
t.Cleanup(func() { _ = serverRaw.Close() })
|
|
||||||
|
|
||||||
client, err := mask.(*noise.Config).WrapPacketConnClient(clientRaw, nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
payload := []byte("real-payload")
|
|
||||||
writeErr := make(chan error, 1)
|
|
||||||
go func() {
|
|
||||||
_, err := client.WriteTo(payload, serverRaw.LocalAddr())
|
|
||||||
writeErr <- err
|
|
||||||
}()
|
|
||||||
|
|
||||||
_ = serverRaw.SetDeadline(time.Now().Add(5 * time.Second))
|
|
||||||
var got [][]byte
|
|
||||||
for i := 0; i < 3; i++ {
|
|
||||||
buf := make([]byte, 64)
|
|
||||||
n, _, err := serverRaw.ReadFrom(buf)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
got = append(got, buf[:n])
|
|
||||||
}
|
|
||||||
if err := <-writeErr; err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bytes.Equal(got[0], []byte{0x16, 0x03, 0x01}) {
|
|
||||||
t.Fatalf("unexpected first noise packet: %x", got[0])
|
|
||||||
}
|
|
||||||
if len(got[1]) != 16 {
|
|
||||||
t.Fatalf("unexpected second noise packet size: got=%d want=16", len(got[1]))
|
|
||||||
}
|
|
||||||
if !bytes.Equal(got[2], payload) {
|
|
||||||
t.Fatalf("unexpected payload: %q", got[2])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ package xdns
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
_ "github.com/xtls/xray-core/common/serial"
|
_ "github.com/xtls/xray-core/common/serial"
|
||||||
internet "github.com/xtls/xray-core/transport/internet"
|
|
||||||
tls "github.com/xtls/xray-core/transport/internet/tls"
|
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
@@ -104,9 +102,6 @@ type ResolverProto struct {
|
|||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
|
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
|
||||||
Addr string `protobuf:"bytes,2,opt,name=addr,proto3" json:"addr,omitempty"`
|
Addr string `protobuf:"bytes,2,opt,name=addr,proto3" json:"addr,omitempty"`
|
||||||
Worker int32 `protobuf:"varint,3,opt,name=worker,proto3" json:"worker,omitempty"`
|
|
||||||
TlsConfig *tls.Config `protobuf:"bytes,4,opt,name=tls_config,json=tlsConfig,proto3" json:"tls_config,omitempty"`
|
|
||||||
QuicConfig *internet.QuicParams `protobuf:"bytes,5,opt,name=quic_config,json=quicConfig,proto3" json:"quic_config,omitempty"`
|
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
@@ -155,27 +150,6 @@ func (x *ResolverProto) GetAddr() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ResolverProto) GetWorker() int32 {
|
|
||||||
if x != nil {
|
|
||||||
return x.Worker
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *ResolverProto) GetTlsConfig() *tls.Config {
|
|
||||||
if x != nil {
|
|
||||||
return x.TlsConfig
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *ResolverProto) GetQuicConfig() *internet.QuicParams {
|
|
||||||
if x != nil {
|
|
||||||
return x.QuicConfig
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Domains []*DomainProto `protobuf:"bytes,1,rep,name=domains,proto3" json:"domains,omitempty"`
|
Domains []*DomainProto `protobuf:"bytes,1,rep,name=domains,proto3" json:"domains,omitempty"`
|
||||||
@@ -240,22 +214,17 @@ var File_transport_internet_finalmask_xdns_config_proto protoreflect.FileDescrip
|
|||||||
|
|
||||||
const file_transport_internet_finalmask_xdns_config_proto_rawDesc = "" +
|
const file_transport_internet_finalmask_xdns_config_proto_rawDesc = "" +
|
||||||
"\n" +
|
"\n" +
|
||||||
".transport/internet/finalmask/xdns/config.proto\x12&xray.transport.internet.finalmask.xdns\x1a!common/serial/typed_message.proto\x1a#transport/internet/tls/config.proto\x1a\x1ftransport/internet/config.proto\"\x8b\x01\n" +
|
".transport/internet/finalmask/xdns/config.proto\x12&xray.transport.internet.finalmask.xdns\x1a!common/serial/typed_message.proto\"\x8b\x01\n" +
|
||||||
"\vDomainProto\x12\x12\n" +
|
"\vDomainProto\x12\x12\n" +
|
||||||
"\x04name\x18\x01 \x01(\tR\x04name\x12\x1b\n" +
|
"\x04name\x18\x01 \x01(\tR\x04name\x12\x1b\n" +
|
||||||
"\tlen_limit\x18\x02 \x01(\x05R\blenLimit\x12\x1f\n" +
|
"\tlen_limit\x18\x02 \x01(\x05R\blenLimit\x12\x1f\n" +
|
||||||
"\vlabel_limit\x18\x03 \x01(\x05R\n" +
|
"\vlabel_limit\x18\x03 \x01(\x05R\n" +
|
||||||
"labelLimit\x12\x14\n" +
|
"labelLimit\x12\x14\n" +
|
||||||
"\x05types\x18\x04 \x03(\x05R\x05types\x12\x14\n" +
|
"\x05types\x18\x04 \x03(\x05R\x05types\x12\x14\n" +
|
||||||
"\x05edns0\x18\x05 \x01(\x05R\x05edns0\"\xd9\x01\n" +
|
"\x05edns0\x18\x05 \x01(\x05R\x05edns0\"7\n" +
|
||||||
"\rResolverProto\x12\x12\n" +
|
"\rResolverProto\x12\x12\n" +
|
||||||
"\x04type\x18\x01 \x01(\tR\x04type\x12\x12\n" +
|
"\x04type\x18\x01 \x01(\tR\x04type\x12\x12\n" +
|
||||||
"\x04addr\x18\x02 \x01(\tR\x04addr\x12\x16\n" +
|
"\x04addr\x18\x02 \x01(\tR\x04addr\"\xcb\x01\n" +
|
||||||
"\x06worker\x18\x03 \x01(\x05R\x06worker\x12B\n" +
|
|
||||||
"\n" +
|
|
||||||
"tls_config\x18\x04 \x01(\v2#.xray.transport.internet.tls.ConfigR\ttlsConfig\x12D\n" +
|
|
||||||
"\vquic_config\x18\x05 \x01(\v2#.xray.transport.internet.QuicParamsR\n" +
|
|
||||||
"quicConfig\"\xcb\x01\n" +
|
|
||||||
"\x06Config\x12M\n" +
|
"\x06Config\x12M\n" +
|
||||||
"\adomains\x18\x01 \x03(\v23.xray.transport.internet.finalmask.xdns.DomainProtoR\adomains\x12S\n" +
|
"\adomains\x18\x01 \x03(\v23.xray.transport.internet.finalmask.xdns.DomainProtoR\adomains\x12S\n" +
|
||||||
"\tresolvers\x18\x02 \x03(\v25.xray.transport.internet.finalmask.xdns.ResolverProtoR\tresolvers\x12\x1d\n" +
|
"\tresolvers\x18\x02 \x03(\v25.xray.transport.internet.finalmask.xdns.ResolverProtoR\tresolvers\x12\x1d\n" +
|
||||||
@@ -280,19 +249,15 @@ var file_transport_internet_finalmask_xdns_config_proto_goTypes = []any{
|
|||||||
(*DomainProto)(nil), // 0: xray.transport.internet.finalmask.xdns.DomainProto
|
(*DomainProto)(nil), // 0: xray.transport.internet.finalmask.xdns.DomainProto
|
||||||
(*ResolverProto)(nil), // 1: xray.transport.internet.finalmask.xdns.ResolverProto
|
(*ResolverProto)(nil), // 1: xray.transport.internet.finalmask.xdns.ResolverProto
|
||||||
(*Config)(nil), // 2: xray.transport.internet.finalmask.xdns.Config
|
(*Config)(nil), // 2: xray.transport.internet.finalmask.xdns.Config
|
||||||
(*tls.Config)(nil), // 3: xray.transport.internet.tls.Config
|
|
||||||
(*internet.QuicParams)(nil), // 4: xray.transport.internet.QuicParams
|
|
||||||
}
|
}
|
||||||
var file_transport_internet_finalmask_xdns_config_proto_depIdxs = []int32{
|
var file_transport_internet_finalmask_xdns_config_proto_depIdxs = []int32{
|
||||||
3, // 0: xray.transport.internet.finalmask.xdns.ResolverProto.tls_config:type_name -> xray.transport.internet.tls.Config
|
0, // 0: xray.transport.internet.finalmask.xdns.Config.domains:type_name -> xray.transport.internet.finalmask.xdns.DomainProto
|
||||||
4, // 1: xray.transport.internet.finalmask.xdns.ResolverProto.quic_config:type_name -> xray.transport.internet.QuicParams
|
1, // 1: xray.transport.internet.finalmask.xdns.Config.resolvers:type_name -> xray.transport.internet.finalmask.xdns.ResolverProto
|
||||||
0, // 2: xray.transport.internet.finalmask.xdns.Config.domains:type_name -> xray.transport.internet.finalmask.xdns.DomainProto
|
2, // [2:2] is the sub-list for method output_type
|
||||||
1, // 3: xray.transport.internet.finalmask.xdns.Config.resolvers:type_name -> xray.transport.internet.finalmask.xdns.ResolverProto
|
2, // [2:2] is the sub-list for method input_type
|
||||||
4, // [4:4] is the sub-list for method output_type
|
2, // [2:2] is the sub-list for extension type_name
|
||||||
4, // [4:4] is the sub-list for method input_type
|
2, // [2:2] is the sub-list for extension extendee
|
||||||
4, // [4:4] is the sub-list for extension type_name
|
0, // [0:2] is the sub-list for field 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_transport_internet_finalmask_xdns_config_proto_init() }
|
func init() { file_transport_internet_finalmask_xdns_config_proto_init() }
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ option java_package = "com.xray.transport.internet.finalmask.xdns";
|
|||||||
option java_multiple_files = true;
|
option java_multiple_files = true;
|
||||||
|
|
||||||
import "common/serial/typed_message.proto";
|
import "common/serial/typed_message.proto";
|
||||||
import "transport/internet/tls/config.proto";
|
|
||||||
import "transport/internet/config.proto";
|
|
||||||
|
|
||||||
message DomainProto {
|
message DomainProto {
|
||||||
string name = 1;
|
string name = 1;
|
||||||
@@ -21,9 +19,6 @@ message DomainProto {
|
|||||||
message ResolverProto {
|
message ResolverProto {
|
||||||
string type = 1;
|
string type = 1;
|
||||||
string addr = 2;
|
string addr = 2;
|
||||||
int32 worker = 3;
|
|
||||||
xray.transport.internet.tls.Config tls_config = 4;
|
|
||||||
xray.transport.internet.QuicParams quic_config = 5;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message Config {
|
message Config {
|
||||||
|
|||||||
@@ -20,8 +20,6 @@ func NewResolver(config *ResolverProto, dialer *finalmask.Dialer) (Resolver, err
|
|||||||
return NewTCPResolver(config, dialer)
|
return NewTCPResolver(config, dialer)
|
||||||
case "udp":
|
case "udp":
|
||||||
return NewUDPResolver(config, dialer)
|
return NewUDPResolver(config, dialer)
|
||||||
case "https":
|
|
||||||
return NewDOHResolver(config, dialer)
|
|
||||||
default:
|
default:
|
||||||
return nil, errors.New("unknown type")
|
return nil, errors.New("unknown type")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,301 +0,0 @@
|
|||||||
package xdns
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
gotls "crypto/tls"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"runtime"
|
|
||||||
"strconv"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/apernet/quic-go"
|
|
||||||
"github.com/apernet/quic-go/http3"
|
|
||||||
"github.com/xtls/xray-core/common"
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
|
||||||
"github.com/xtls/xray-core/common/net"
|
|
||||||
"github.com/xtls/xray-core/transport/internet"
|
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask"
|
|
||||||
"github.com/xtls/xray-core/transport/internet/hysteria/congestion"
|
|
||||||
"github.com/xtls/xray-core/transport/internet/hysteria/congestion/bbr"
|
|
||||||
"github.com/xtls/xray-core/transport/internet/tls"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DOHResolver struct {
|
|
||||||
ctx context.Context
|
|
||||||
cancel func()
|
|
||||||
client *http.Client
|
|
||||||
url string
|
|
||||||
time time.Time
|
|
||||||
|
|
||||||
udpAddr *net.UDPAddr
|
|
||||||
readCh chan []byte
|
|
||||||
sendCh chan []byte
|
|
||||||
closeCh chan struct{}
|
|
||||||
wg sync.WaitGroup
|
|
||||||
mu sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDOHResolver(config *ResolverProto, dialer *finalmask.Dialer) (Resolver, error) {
|
|
||||||
if config.Worker < 1 {
|
|
||||||
return nil, errors.New("invalid worker")
|
|
||||||
}
|
|
||||||
if config.TlsConfig == nil {
|
|
||||||
return nil, errors.New("empty tls config")
|
|
||||||
}
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
var tr http.RoundTripper
|
|
||||||
var setaddr func(addr net.Addr)
|
|
||||||
switch {
|
|
||||||
case len(config.TlsConfig.NextProtocol) == 1 && config.TlsConfig.NextProtocol[0] == "h3":
|
|
||||||
quicParams := config.QuicConfig
|
|
||||||
if quicParams == nil {
|
|
||||||
quicParams = &internet.QuicParams{}
|
|
||||||
}
|
|
||||||
quicConfig := &quic.Config{
|
|
||||||
InitialStreamReceiveWindow: quicParams.InitStreamReceiveWindow,
|
|
||||||
MaxStreamReceiveWindow: quicParams.MaxStreamReceiveWindow,
|
|
||||||
InitialConnectionReceiveWindow: quicParams.InitConnReceiveWindow,
|
|
||||||
MaxConnectionReceiveWindow: quicParams.MaxConnReceiveWindow,
|
|
||||||
MaxIdleTimeout: time.Duration(quicParams.MaxIdleTimeout) * time.Second,
|
|
||||||
KeepAlivePeriod: time.Duration(quicParams.KeepAlivePeriod) * time.Second,
|
|
||||||
DisablePathMTUDiscovery: quicParams.DisablePathMtuDiscovery || (runtime.GOOS != "linux" && runtime.GOOS != "windows" && runtime.GOOS != "darwin"),
|
|
||||||
ChromeParrot: !quicParams.DisableChromeParrot,
|
|
||||||
}
|
|
||||||
tr = &http3.Transport{
|
|
||||||
TLSClientConfig: config.TlsConfig.GetTLSConfig(),
|
|
||||||
QUICConfig: quicConfig,
|
|
||||||
Dial: func(ctx context.Context, addr string, tlsCfg *gotls.Config, cfg *quic.Config) (*quic.Conn, error) {
|
|
||||||
dest, err := net.ParseDestination("udp:" + addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
conn, err := dialer.DialUDP(dest)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
setaddr(conn.RemoteAddr())
|
|
||||||
tr := &quic.Transport{Conn: conn.(*net.PacketConnWrapper).PacketConn, DisableGSO: quicParams.DisableGSO}
|
|
||||||
if !quicParams.DisableChromeParrot {
|
|
||||||
tr.ConnectionIDGenerator = quic.ZeroLengthConnectionIDGenerator{}
|
|
||||||
tlsCfg.GetCertificate = nil
|
|
||||||
}
|
|
||||||
qc, err := tr.Dial(ctx, conn.RemoteAddr(), tlsCfg, cfg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
context.AfterFunc(qc.Context(), func() { tr.Close(); conn.Close() })
|
|
||||||
switch quicParams.Congestion {
|
|
||||||
case "", "reno":
|
|
||||||
case "bbr":
|
|
||||||
congestion.UseBBR(qc, bbr.Profile(quicParams.BbrProfile))
|
|
||||||
case "force-brutal":
|
|
||||||
congestion.UseBrutal(qc, quicParams.BrutalUp, quicParams.BrutalDisableLossCompensation)
|
|
||||||
default:
|
|
||||||
panic(quicParams.Congestion)
|
|
||||||
}
|
|
||||||
return qc, nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
tlsConfig := config.TlsConfig.GetTLSConfig()
|
|
||||||
tr = &http.Transport{
|
|
||||||
ForceAttemptHTTP2: true,
|
|
||||||
DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
||||||
dest, err := net.ParseDestination("tcp:" + addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
conn, err := dialer.DialTCP(dest)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
setaddr(conn.RemoteAddr())
|
|
||||||
var tlsConn net.Conn
|
|
||||||
if fingerprint := tls.GetFingerprint(config.TlsConfig.Fingerprint); fingerprint != nil {
|
|
||||||
tlsConn = tls.UClient(conn, tlsConfig, fingerprint)
|
|
||||||
err = tlsConn.(*tls.UConn).HandshakeContext(ctx)
|
|
||||||
} else {
|
|
||||||
tlsConn = gotls.Client(conn, tlsConfig)
|
|
||||||
err = tlsConn.(*gotls.Conn).HandshakeContext(ctx)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
_ = conn.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return tlsConn, nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
client := &http.Client{
|
|
||||||
Transport: tr,
|
|
||||||
}
|
|
||||||
r := &DOHResolver{
|
|
||||||
ctx: ctx,
|
|
||||||
cancel: cancel,
|
|
||||||
client: client,
|
|
||||||
url: config.Addr,
|
|
||||||
|
|
||||||
udpAddr: &net.UDPAddr{IP: []byte{0, 0, 0, 0}},
|
|
||||||
readCh: make(chan []byte),
|
|
||||||
sendCh: make(chan []byte),
|
|
||||||
closeCh: make(chan struct{}),
|
|
||||||
}
|
|
||||||
for range config.Worker {
|
|
||||||
r.wg.Add(1)
|
|
||||||
go r.worker()
|
|
||||||
}
|
|
||||||
setaddr = r.setaddr
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DOHResolver) closed() bool {
|
|
||||||
select {
|
|
||||||
case <-r.closeCh:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DOHResolver) setaddr(addr net.Addr) {
|
|
||||||
r.mu.Lock()
|
|
||||||
defer r.mu.Unlock()
|
|
||||||
switch v := addr.(type) {
|
|
||||||
case *net.TCPAddr:
|
|
||||||
r.udpAddr = &net.UDPAddr{IP: v.IP, Port: v.Port}
|
|
||||||
case *net.UDPAddr:
|
|
||||||
r.udpAddr = v
|
|
||||||
default:
|
|
||||||
panic(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DOHResolver) cooldown(t time.Time) bool {
|
|
||||||
r.mu.Lock()
|
|
||||||
defer r.mu.Unlock()
|
|
||||||
if t == (time.Time{}) {
|
|
||||||
return time.Now().Before(r.time)
|
|
||||||
}
|
|
||||||
if t.After(r.time) {
|
|
||||||
r.time = t
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DOHResolver) do(p []byte) error {
|
|
||||||
req := common.Must2(http.NewRequestWithContext(r.ctx, "POST", r.url, bytes.NewReader(p)))
|
|
||||||
req.Header.Set("Accept", "application/dns-message")
|
|
||||||
req.Header.Set("Content-Type", "application/dns-message")
|
|
||||||
req.Header.Set("User-Agent", "")
|
|
||||||
resp, err := r.client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
switch resp.StatusCode {
|
|
||||||
case http.StatusOK:
|
|
||||||
p := pool4K.Get().([]byte)
|
|
||||||
off := 0
|
|
||||||
for {
|
|
||||||
n, err := resp.Body.Read(p[off:])
|
|
||||||
if n > 0 {
|
|
||||||
off += n
|
|
||||||
}
|
|
||||||
if off == len(p) || err != nil {
|
|
||||||
if err != io.EOF {
|
|
||||||
pool4K.Put(p[:cap(p)])
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-r.closeCh:
|
|
||||||
pool4K.Put(p[:cap(p)])
|
|
||||||
case r.readCh <- p[:off]:
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
case http.StatusTooManyRequests:
|
|
||||||
if value := resp.Header.Get("Retry-After"); value != "" {
|
|
||||||
if t, err := http.ParseTime(value); err == nil {
|
|
||||||
r.cooldown(t)
|
|
||||||
} else if i, err := strconv.ParseUint(value, 10, 32); err == nil {
|
|
||||||
r.cooldown(time.Now().Add(time.Duration(i) * time.Second))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return errors.New("code ", resp.StatusCode, " header ", resp.Header)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DOHResolver) worker() {
|
|
||||||
defer r.wg.Done()
|
|
||||||
|
|
||||||
var p []byte
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-r.closeCh:
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
select {
|
|
||||||
case <-r.closeCh:
|
|
||||||
return
|
|
||||||
case p = <-r.sendCh:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if r.cooldown(time.Time{}) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := r.do(p); err != nil {
|
|
||||||
errors.LogErrorInner(context.Background(), err, "do err")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DOHResolver) Addr() *net.UDPAddr {
|
|
||||||
r.mu.Lock()
|
|
||||||
defer r.mu.Unlock()
|
|
||||||
return r.udpAddr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DOHResolver) Read(p []byte) (n int, err error) {
|
|
||||||
packet, ok := <-r.readCh
|
|
||||||
if ok {
|
|
||||||
n = copy(p, packet)
|
|
||||||
pool4K.Put(packet[:cap(packet)])
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
return 0, io.ErrClosedPipe
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DOHResolver) Send(p []byte) {
|
|
||||||
select {
|
|
||||||
case <-r.closeCh:
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
b := make([]byte, len(p))
|
|
||||||
copy(b, p)
|
|
||||||
select {
|
|
||||||
case <-r.closeCh:
|
|
||||||
return
|
|
||||||
case r.sendCh <- b:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DOHResolver) Close() {
|
|
||||||
r.mu.Lock()
|
|
||||||
if r.closed() {
|
|
||||||
r.mu.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
close(r.closeCh)
|
|
||||||
r.mu.Unlock()
|
|
||||||
r.cancel()
|
|
||||||
r.wg.Wait()
|
|
||||||
r.client.CloseIdleConnections()
|
|
||||||
close(r.readCh)
|
|
||||||
// close(r.sendCh)
|
|
||||||
}
|
|
||||||
@@ -22,12 +22,6 @@ type interConn struct {
|
|||||||
|
|
||||||
client bool
|
client bool
|
||||||
user *protocol.MemoryUser
|
user *protocol.MemoryUser
|
||||||
lock chan struct{}
|
|
||||||
mu sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *interConn) unlock() {
|
|
||||||
<-c.lock
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *interConn) User() *protocol.MemoryUser {
|
func (c *interConn) User() *protocol.MemoryUser {
|
||||||
@@ -39,9 +33,6 @@ func (c *interConn) Read(b []byte) (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *interConn) Write(b []byte) (int, error) {
|
func (c *interConn) Write(b []byte) (int, error) {
|
||||||
c.lock <- struct{}{}
|
|
||||||
defer c.unlock()
|
|
||||||
|
|
||||||
if c.client {
|
if c.client {
|
||||||
c.client = false
|
c.client = false
|
||||||
if _, err := c.stream.Write(append(quicvarint.Append(nil, FrameTypeTCPRequest), b...)); err != nil {
|
if _, err := c.stream.Write(append(quicvarint.Append(nil, FrameTypeTCPRequest), b...)); err != nil {
|
||||||
@@ -49,20 +40,11 @@ func (c *interConn) Write(b []byte) (int, error) {
|
|||||||
}
|
}
|
||||||
return len(b), nil
|
return len(b), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.stream.Write(b)
|
return c.stream.Write(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *interConn) Close() error {
|
func (c *interConn) Close() error {
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
select {
|
|
||||||
case c.lock <- struct{}{}:
|
|
||||||
defer c.unlock()
|
|
||||||
default:
|
|
||||||
c.stream.CancelRead(0)
|
|
||||||
c.stream.CancelWrite(0)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
c.stream.CancelRead(0)
|
c.stream.CancelRead(0)
|
||||||
return c.stream.Close()
|
return c.stream.Close()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -257,7 +257,6 @@ func (c *client) tcp(ctx context.Context) (stat.Connection, error) {
|
|||||||
remote: c.conn.RemoteAddr(),
|
remote: c.conn.RemoteAddr(),
|
||||||
|
|
||||||
client: true,
|
client: true,
|
||||||
lock: make(chan struct{}, 1),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -134,7 +134,6 @@ func (h *httpHandler) StreamDispatcher(ft http3.FrameType, stream *quic.Stream,
|
|||||||
remote: h.conn.RemoteAddr(),
|
remote: h.conn.RemoteAddr(),
|
||||||
|
|
||||||
user: h.user,
|
user: h.user,
|
||||||
lock: make(chan struct{}, 1),
|
|
||||||
})
|
})
|
||||||
return true, nil
|
return true, nil
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -2,13 +2,12 @@ package reality
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/ecdh"
|
|
||||||
"crypto/mldsa"
|
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/cloudflare/circl/sign/mldsa/mldsa65"
|
||||||
"github.com/xtls/reality"
|
"github.com/xtls/reality"
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/transport/internet"
|
"github.com/xtls/xray-core/transport/internet"
|
||||||
@@ -24,6 +23,7 @@ func (c *Config) GetREALITYConfig() *reality.Config {
|
|||||||
Dest: c.Dest,
|
Dest: c.Dest,
|
||||||
Xver: byte(c.Xver),
|
Xver: byte(c.Xver),
|
||||||
|
|
||||||
|
PrivateKey: c.PrivateKey,
|
||||||
MinClientVer: c.MinClientVer,
|
MinClientVer: c.MinClientVer,
|
||||||
MaxClientVer: c.MaxClientVer,
|
MaxClientVer: c.MaxClientVer,
|
||||||
MaxTimeDiff: time.Duration(c.MaxTimeDiff) * time.Millisecond,
|
MaxTimeDiff: time.Duration(c.MaxTimeDiff) * time.Millisecond,
|
||||||
@@ -33,14 +33,9 @@ func (c *Config) GetREALITYConfig() *reality.Config {
|
|||||||
|
|
||||||
KeyLogWriter: KeyLogWriterFromConfig(c),
|
KeyLogWriter: KeyLogWriterFromConfig(c),
|
||||||
}
|
}
|
||||||
var err error
|
|
||||||
if config.PrivateKey, err = ecdh.X25519().NewPrivateKey(c.PrivateKey); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
if c.Mldsa65Seed != nil {
|
if c.Mldsa65Seed != nil {
|
||||||
if config.Mldsa65Key, err = mldsa.NewPrivateKey(mldsa.MLDSA65(), c.Mldsa65Seed); err != nil {
|
_, key := mldsa65.NewKeyFromSeed((*[32]byte)(c.Mldsa65Seed))
|
||||||
panic(err)
|
config.Mldsa65Key = key.Bytes()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if c.LimitFallbackUpload != nil {
|
if c.LimitFallbackUpload != nil {
|
||||||
config.LimitFallbackUpload.AfterBytes = c.LimitFallbackUpload.AfterBytes
|
config.LimitFallbackUpload.AfterBytes = c.LimitFallbackUpload.AfterBytes
|
||||||
@@ -68,7 +63,7 @@ func KeyLogWriterFromConfig(c *Config) io.Writer {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
writer, err := os.OpenFile(c.MasterKeyLog, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0o600)
|
writer, err := os.OpenFile(c.MasterKeyLog, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0o644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errors.LogErrorInner(context.Background(), err, "failed to open ", c.MasterKeyLog, " as master key log")
|
errors.LogErrorInner(context.Background(), err, "failed to open ", c.MasterKeyLog, " as master key log")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/ecdh"
|
"crypto/ecdh"
|
||||||
"crypto/ed25519"
|
"crypto/ed25519"
|
||||||
"crypto/hkdf"
|
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"crypto/mldsa"
|
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
gotls "crypto/tls"
|
gotls "crypto/tls"
|
||||||
@@ -24,6 +22,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/cloudflare/circl/sign/mldsa/mldsa65"
|
||||||
utls "github.com/refraction-networking/utls"
|
utls "github.com/refraction-networking/utls"
|
||||||
"github.com/xtls/reality"
|
"github.com/xtls/reality"
|
||||||
"github.com/xtls/xray-core/common/crypto"
|
"github.com/xtls/xray-core/common/crypto"
|
||||||
@@ -32,6 +31,7 @@ import (
|
|||||||
"github.com/xtls/xray-core/common/utils"
|
"github.com/xtls/xray-core/common/utils"
|
||||||
"github.com/xtls/xray-core/core"
|
"github.com/xtls/xray-core/core"
|
||||||
"github.com/xtls/xray-core/transport/internet/tls"
|
"github.com/xtls/xray-core/transport/internet/tls"
|
||||||
|
"golang.org/x/crypto/hkdf"
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -114,8 +114,8 @@ func (c *UConn) VerifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x50
|
|||||||
if len(certs[0].Extensions) > 0 {
|
if len(certs[0].Extensions) > 0 {
|
||||||
h.Write(c.HandshakeState.Hello.Raw)
|
h.Write(c.HandshakeState.Hello.Raw)
|
||||||
h.Write(c.HandshakeState.ServerHello.Raw)
|
h.Write(c.HandshakeState.ServerHello.Raw)
|
||||||
verify, _ := mldsa.NewPublicKey(mldsa.MLDSA65(), c.Config.Mldsa65Verify)
|
verify, _ := mldsa65.Scheme().UnmarshalBinaryPublicKey(c.Config.Mldsa65Verify)
|
||||||
if mldsa.Verify(verify, h.Sum(nil), certs[0].Extensions[0].Value, nil) == nil {
|
if mldsa65.Verify(verify.(*mldsa65.PublicKey), h.Sum(nil), nil, certs[0].Extensions[0].Value) {
|
||||||
c.Verified = true
|
c.Verified = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -176,7 +176,7 @@ func UClient(c net.Conn, config *Config, ctx context.Context, dest net.Destinati
|
|||||||
}
|
}
|
||||||
publicKey, err := ecdh.X25519().NewPublicKey(config.PublicKey)
|
publicKey, err := ecdh.X25519().NewPublicKey(config.PublicKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("REALITY: nil publicKey").Base(err)
|
return nil, errors.New("REALITY: publicKey == nil")
|
||||||
}
|
}
|
||||||
ecdhe := uConn.HandshakeState.State13.KeyShareKeys.Ecdhe
|
ecdhe := uConn.HandshakeState.State13.KeyShareKeys.Ecdhe
|
||||||
if ecdhe == nil {
|
if ecdhe == nil {
|
||||||
@@ -185,11 +185,12 @@ func UClient(c net.Conn, config *Config, ctx context.Context, dest net.Destinati
|
|||||||
if ecdhe == nil {
|
if ecdhe == nil {
|
||||||
return nil, errors.New("Current fingerprint ", uConn.ClientHelloID.Client, uConn.ClientHelloID.Version, " does not support TLS 1.3, REALITY handshake cannot establish.")
|
return nil, errors.New("Current fingerprint ", uConn.ClientHelloID.Client, uConn.ClientHelloID.Version, " does not support TLS 1.3, REALITY handshake cannot establish.")
|
||||||
}
|
}
|
||||||
if uConn.AuthKey, err = ecdhe.ECDH(publicKey); err != nil {
|
uConn.AuthKey, _ = ecdhe.ECDH(publicKey)
|
||||||
return nil, errors.New("REALITY: nil sharedKey").Base(err)
|
if uConn.AuthKey == nil {
|
||||||
|
return nil, errors.New("REALITY: SharedKey == nil")
|
||||||
}
|
}
|
||||||
if uConn.AuthKey, err = hkdf.Key(sha256.New, uConn.AuthKey, hello.Random[:20], "REALITY", 32); err != nil {
|
if _, err := hkdf.New(sha256.New, uConn.AuthKey, hello.Random[:20], []byte("REALITY")).Read(uConn.AuthKey); err != nil {
|
||||||
return nil, errors.New("REALITY: nil authKey").Base(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
aead := crypto.NewAesGcm(uConn.AuthKey)
|
aead := crypto.NewAesGcm(uConn.AuthKey)
|
||||||
if config.Show {
|
if config.Show {
|
||||||
|
|||||||
@@ -28,22 +28,3 @@ func (v *SocketConfig) ParseTFOValue() int {
|
|||||||
}
|
}
|
||||||
return tfo
|
return tfo
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dial should fail on bind errors, return this to indicate such errors.
|
|
||||||
type BindError struct {
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *BindError) Unwrap() error {
|
|
||||||
return e.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *BindError) Error() string {
|
|
||||||
return "failed to bind to interface: " + e.err.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewBindError(err error) *BindError {
|
|
||||||
return &BindError{
|
|
||||||
err: err,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -136,15 +136,15 @@ func applyOutboundSocketOptions(network string, address string, fd uintptr, conf
|
|||||||
if config.Interface != "" {
|
if config.Interface != "" {
|
||||||
iface, err := net.InterfaceByName(config.Interface)
|
iface, err := net.InterfaceByName(config.Interface)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return NewBindError(errors.New("failed to get interface ", config.Interface).Base(err))
|
return errors.New("failed to get interface ", config.Interface).Base(err)
|
||||||
}
|
}
|
||||||
if network == "tcp6" || network == "udp6" {
|
if network == "tcp6" || network == "udp6" {
|
||||||
if err := unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF, iface.Index); err != nil {
|
if err := unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF, iface.Index); err != nil {
|
||||||
return NewBindError(errors.New("failed to set IPV6_BOUND_IF").Base(err))
|
return errors.New("failed to set IPV6_BOUND_IF").Base(err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if err := unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_BOUND_IF, iface.Index); err != nil {
|
if err := unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_BOUND_IF, iface.Index); err != nil {
|
||||||
return NewBindError(errors.New("failed to set IP_BOUND_IF").Base(err))
|
return errors.New("failed to set IP_BOUND_IF").Base(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ func applyOutboundSocketOptions(network string, address string, fd uintptr, conf
|
|||||||
|
|
||||||
if config.Interface != "" {
|
if config.Interface != "" {
|
||||||
if err := syscall.BindToDevice(int(fd), config.Interface); err != nil {
|
if err := syscall.BindToDevice(int(fd), config.Interface); err != nil {
|
||||||
return NewBindError(errors.New("failed to set Interface").Base(err))
|
return errors.New("failed to set Interface").Base(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ func applyOutboundSocketOptions(network string, address string, fd uintptr, conf
|
|||||||
if config.Interface != "" {
|
if config.Interface != "" {
|
||||||
inf, err := net.InterfaceByName(config.Interface)
|
inf, err := net.InterfaceByName(config.Interface)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return NewBindError(errors.New("failed to find the interface").Base(err))
|
return errors.New("failed to find the interface").Base(err)
|
||||||
}
|
}
|
||||||
// easy way to check if the address is ipv4
|
// easy way to check if the address is ipv4
|
||||||
isV4 := strings.Contains(address, ".")
|
isV4 := strings.Contains(address, ".")
|
||||||
@@ -47,20 +47,20 @@ func applyOutboundSocketOptions(network string, address string, fd uintptr, conf
|
|||||||
binary.BigEndian.PutUint32(bytes[:], uint32(inf.Index))
|
binary.BigEndian.PutUint32(bytes[:], uint32(inf.Index))
|
||||||
idx := *(*uint32)(unsafe.Pointer(&bytes[0]))
|
idx := *(*uint32)(unsafe.Pointer(&bytes[0]))
|
||||||
if err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_IP, IP_UNICAST_IF, int(idx)); err != nil {
|
if err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_IP, IP_UNICAST_IF, int(idx)); err != nil {
|
||||||
return NewBindError(errors.New("failed to set IP_UNICAST_IF").Base(err))
|
return errors.New("failed to set IP_UNICAST_IF").Base(err)
|
||||||
}
|
}
|
||||||
if ip := net.ParseIP(host); ip != nil && ip.IsMulticast() && isUDPSocket(network) {
|
if ip := net.ParseIP(host); ip != nil && ip.IsMulticast() && isUDPSocket(network) {
|
||||||
if err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_IP, syscall.IP_MULTICAST_IF, int(idx)); err != nil {
|
if err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_IP, syscall.IP_MULTICAST_IF, int(idx)); err != nil {
|
||||||
return NewBindError(errors.New("failed to set IP_MULTICAST_IF").Base(err))
|
return errors.New("failed to set IP_MULTICAST_IF").Base(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_IPV6, IPV6_UNICAST_IF, inf.Index); err != nil {
|
if err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_IPV6, IPV6_UNICAST_IF, inf.Index); err != nil {
|
||||||
return NewBindError(errors.New("failed to set IPV6_UNICAST_IF").Base(err))
|
return errors.New("failed to set IPV6_UNICAST_IF").Base(err)
|
||||||
}
|
}
|
||||||
if ip := net.ParseIP(host); ip != nil && ip.IsMulticast() && isUDPSocket(network) {
|
if ip := net.ParseIP(host); ip != nil && ip.IsMulticast() && isUDPSocket(network) {
|
||||||
if err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_IPV6, syscall.IPV6_MULTICAST_IF, inf.Index); err != nil {
|
if err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_IPV6, syscall.IPV6_MULTICAST_IF, inf.Index); err != nil {
|
||||||
return NewBindError(errors.New("failed to set IPV6_MULTICAST_IF").Base(err))
|
return errors.New("failed to set IPV6_MULTICAST_IF").Base(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,28 +68,19 @@ func (d *DefaultSystemDialer) Dial(ctx context.Context, src net.Address, dest ne
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
var lc net.ListenConfig
|
var lc net.ListenConfig
|
||||||
if sockopt != nil || len(Controllers) > 0 {
|
lc.Control = func(network, address string, c syscall.RawConn) error {
|
||||||
lc.Control = func(network, _ string, c syscall.RawConn) error {
|
|
||||||
// ListenPacket supplies a local address; dialer controllers need the destination.
|
|
||||||
for _, ctl := range Controllers {
|
for _, ctl := range Controllers {
|
||||||
if err := ctl(network, destAddr.String(), c); err != nil {
|
if err := ctl(network, address, c); err != nil {
|
||||||
return errors.New("failed to apply external controller").Base(err)
|
errors.LogInfoInner(ctx, err, "failed to apply external controller")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return c.Control(func(fd uintptr) {
|
||||||
if sockopt != nil {
|
if sockopt != nil {
|
||||||
var sockErr, controlErr error
|
if err := applyOutboundSocketOptions(network, destAddr.String(), fd, sockopt); err != nil {
|
||||||
controlErr = c.Control(func(fd uintptr) {
|
errors.LogInfo(ctx, err, "failed to apply socket options")
|
||||||
if sockErr = applyOutboundSocketOptions(network, destAddr.String(), fd, sockopt); sockErr != nil {
|
}
|
||||||
errors.LogInfoInner(ctx, sockErr, "failed to apply socket options")
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if _, ok := sockErr.(*BindError); ok {
|
|
||||||
return sockErr
|
|
||||||
}
|
|
||||||
return controlErr
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
packetConn, err := lc.ListenPacket(ctx, srcAddr.Network(), srcAddr.String())
|
packetConn, err := lc.ListenPacket(ctx, srcAddr.Network(), srcAddr.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -137,22 +128,16 @@ func (d *DefaultSystemDialer) Dial(ctx context.Context, src net.Address, dest ne
|
|||||||
dialer.Control = func(network, address string, c syscall.RawConn) error {
|
dialer.Control = func(network, address string, c syscall.RawConn) error {
|
||||||
for _, ctl := range Controllers {
|
for _, ctl := range Controllers {
|
||||||
if err := ctl(network, address, c); err != nil {
|
if err := ctl(network, address, c); err != nil {
|
||||||
return errors.New("failed to apply external controller").Base(err)
|
errors.LogInfoInner(ctx, err, "failed to apply external controller")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return c.Control(func(fd uintptr) {
|
||||||
if sockopt != nil {
|
if sockopt != nil {
|
||||||
var sockErr, controlErr error
|
if err := applyOutboundSocketOptions(network, address, fd, sockopt); err != nil {
|
||||||
controlErr = c.Control(func(fd uintptr) {
|
errors.LogInfoInner(ctx, err, "failed to apply socket options")
|
||||||
if sockErr = applyOutboundSocketOptions(network, address, fd, sockopt); sockErr != nil {
|
}
|
||||||
errors.LogInfoInner(ctx, sockErr, "failed to apply socket options")
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if _, ok := sockErr.(*BindError); ok {
|
|
||||||
return sockErr
|
|
||||||
}
|
|
||||||
return controlErr
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,7 +183,6 @@ func UseAlternativeSystemDialer(dialer SystemDialer) {
|
|||||||
|
|
||||||
// RegisterDialerController adds a controller to the effective system dialer.
|
// RegisterDialerController adds a controller to the effective system dialer.
|
||||||
// The controller can be used to operate on file descriptors before they are put into use.
|
// The controller can be used to operate on file descriptors before they are put into use.
|
||||||
// It receives the network and resolved remote address, as in net.Dialer.Control.
|
|
||||||
// It only works when effective dialer is the default dialer.
|
// It only works when effective dialer is the default dialer.
|
||||||
//
|
//
|
||||||
// xray:api:beta
|
// xray:api:beta
|
||||||
|
|||||||
@@ -466,7 +466,7 @@ func (c *Config) GetTLSConfig(opts ...Option) *tls.Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(c.MasterKeyLog) > 0 && c.MasterKeyLog != "none" {
|
if len(c.MasterKeyLog) > 0 && c.MasterKeyLog != "none" {
|
||||||
writer, err := os.OpenFile(c.MasterKeyLog, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0o600)
|
writer, err := os.OpenFile(c.MasterKeyLog, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0o644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errors.LogErrorInner(context.Background(), err, "failed to open ", c.MasterKeyLog, " as master key log")
|
errors.LogErrorInner(context.Background(), err, "failed to open ", c.MasterKeyLog, " as master key log")
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
// grpcUtlsInfo contains the auth information for a TLS authenticated connection.
|
// grpcUtlsInfo contains the auth information for a TLS authenticated connection.
|
||||||
// It implements the AuthInfo interface.
|
// It implements the AuthInfo interface.
|
||||||
type grpcUtlsInfo struct {
|
type grpcUtlsInfo struct {
|
||||||
State gotls.ConnectionState
|
State utls.ConnectionState
|
||||||
credentials.CommonAuthInfo
|
credentials.CommonAuthInfo
|
||||||
// This API is experimental.
|
// This API is experimental.
|
||||||
SPIFFEID *url.URL
|
SPIFFEID *url.URL
|
||||||
|
|||||||
@@ -154,24 +154,6 @@ func (c *UConn) NegotiatedProtocol() string {
|
|||||||
return state.NegotiatedProtocol
|
return state.NegotiatedProtocol
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UConn) ConnectionState() tls.ConnectionState {
|
|
||||||
s := c.UConn.ConnectionState()
|
|
||||||
var state tls.ConnectionState
|
|
||||||
state.HandshakeComplete = s.HandshakeComplete
|
|
||||||
state.Version = s.Version
|
|
||||||
state.NegotiatedProtocol = s.NegotiatedProtocol
|
|
||||||
state.DidResume = s.DidResume
|
|
||||||
state.ServerName = s.ServerName
|
|
||||||
state.CipherSuite = s.CipherSuite
|
|
||||||
state.PeerCertificates = s.PeerCertificates
|
|
||||||
state.VerifiedChains = s.VerifiedChains
|
|
||||||
state.SignedCertificateTimestamps = s.SignedCertificateTimestamps
|
|
||||||
state.OCSPResponse = s.OCSPResponse
|
|
||||||
state.TLSUnique = s.TLSUnique
|
|
||||||
state.ECHAccepted = s.ECHAccepted
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
|
|
||||||
func UClient(c net.Conn, config *tls.Config, fingerprint *utls.ClientHelloID) net.Conn {
|
func UClient(c net.Conn, config *tls.Config, fingerprint *utls.ClientHelloID) net.Conn {
|
||||||
utlsConn := utls.UClient(c, copyConfig(config), *fingerprint)
|
utlsConn := utls.UClient(c, copyConfig(config), *fingerprint)
|
||||||
return &UConn{UConn: utlsConn}
|
return &UConn{UConn: utlsConn}
|
||||||
|
|||||||
@@ -3,156 +3,46 @@ package xdrive
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
gotls "crypto/tls"
|
gotls "crypto/tls"
|
||||||
"io"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
|
||||||
"runtime"
|
|
||||||
"slices"
|
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/apernet/quic-go"
|
|
||||||
"github.com/apernet/quic-go/http3"
|
|
||||||
"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"
|
||||||
"github.com/xtls/xray-core/common/net/cnc"
|
|
||||||
"github.com/xtls/xray-core/common/utils"
|
|
||||||
"github.com/xtls/xray-core/transport/internet"
|
"github.com/xtls/xray-core/transport/internet"
|
||||||
"github.com/xtls/xray-core/transport/internet/hysteria/congestion"
|
"github.com/xtls/xray-core/transport/internet/reality"
|
||||||
"github.com/xtls/xray-core/transport/internet/hysteria/congestion/bbr"
|
|
||||||
"github.com/xtls/xray-core/transport/internet/splithttp"
|
|
||||||
"github.com/xtls/xray-core/transport/internet/tls"
|
"github.com/xtls/xray-core/transport/internet/tls"
|
||||||
|
"golang.org/x/net/http2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var errStalled = errors.New("the request made no progress")
|
|
||||||
|
|
||||||
type serviceTransport struct {
|
type serviceTransport struct {
|
||||||
plain http.RoundTripper
|
plain http.RoundTripper
|
||||||
secure http.RoundTripper
|
secure http.RoundTripper
|
||||||
stall time.Duration
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RoundTrip ends a request that stands still for t.stall, with no answer that
|
|
||||||
// long after its body went out or no byte of either body, but not one that just
|
|
||||||
// runs long: a large object on a slow link takes minutes and is fine as long as
|
|
||||||
// its bytes move
|
|
||||||
func (t *serviceTransport) RoundTrip(r *http.Request) (*http.Response, error) {
|
func (t *serviceTransport) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||||
next := t.plain
|
|
||||||
if r.URL.Scheme == "https" {
|
if r.URL.Scheme == "https" {
|
||||||
next = t.secure
|
return t.secure.RoundTrip(r)
|
||||||
|
}
|
||||||
|
return t.plain.RoundTrip(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithCancelCause(r.Context())
|
func newServiceClient(streamSettings *internet.MemoryStreamConfig, timeout time.Duration, maxConns int) *http.Client {
|
||||||
timer := time.AfterFunc(t.stall, func() { cancel(errStalled) })
|
|
||||||
alive := func() { timer.Reset(t.stall) }
|
|
||||||
end := func() {
|
|
||||||
timer.Stop()
|
|
||||||
cancel(nil)
|
|
||||||
}
|
|
||||||
r = r.WithContext(ctx)
|
|
||||||
// the headers of chrome as with XHTTP and DoH, net/http and http3 would send
|
|
||||||
// their own user agent after a hello of chrome
|
|
||||||
r.Header = r.Header.Clone()
|
|
||||||
utils.TryDefaultHeadersWith(r.Header, "fetch")
|
|
||||||
if r.Body != nil && r.Body != http.NoBody {
|
|
||||||
r.Body = &progressBody{ReadCloser: r.Body, alive: alive}
|
|
||||||
}
|
|
||||||
// a body sent again by the transport itself, after a GOAWAY or on a reused
|
|
||||||
// connection that failed, moves just the same
|
|
||||||
if getBody := r.GetBody; getBody != nil {
|
|
||||||
r.GetBody = func() (io.ReadCloser, error) {
|
|
||||||
body, err := getBody()
|
|
||||||
if err != nil || body == http.NoBody {
|
|
||||||
return body, err
|
|
||||||
}
|
|
||||||
return &progressBody{ReadCloser: body, alive: alive}, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := next.RoundTrip(r)
|
|
||||||
if err != nil {
|
|
||||||
err = stalled(ctx, err)
|
|
||||||
end()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
alive()
|
|
||||||
resp.Body = &progressBody{ReadCloser: resp.Body, alive: alive, end: end, ctx: ctx}
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// stalled returns errStalled for an error that the stall gave, which http2 reports
|
|
||||||
// as a plain cancel
|
|
||||||
func stalled(ctx context.Context, err error) error {
|
|
||||||
if context.Cause(ctx) == errStalled {
|
|
||||||
return errStalled
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
type progressBody struct {
|
|
||||||
io.ReadCloser
|
|
||||||
alive func()
|
|
||||||
end func()
|
|
||||||
// ctx is that of the request, for the body of its response
|
|
||||||
ctx context.Context
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *progressBody) Read(p []byte) (int, error) {
|
|
||||||
// http2 reads a request body up to 512 KiB at a time and sends it all before
|
|
||||||
// the next read, which on a slow link could look like no progress
|
|
||||||
n, err := b.ReadCloser.Read(p[:min(len(p), 16<<10)])
|
|
||||||
if n > 0 {
|
|
||||||
b.alive()
|
|
||||||
}
|
|
||||||
if err != nil && err != io.EOF && b.ctx != nil {
|
|
||||||
err = stalled(b.ctx, err)
|
|
||||||
}
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *progressBody) Close() error {
|
|
||||||
err := b.ReadCloser.Close()
|
|
||||||
if b.end != nil {
|
|
||||||
b.end()
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// newServiceClient returns a client whose requests end after stall without
|
|
||||||
// progress, see serviceTransport.RoundTrip, and whose connections close when
|
|
||||||
// connCtx is done. Clients that share h1Hosts share which hosts answer over h1
|
|
||||||
func newServiceClient(connCtx context.Context, h1Hosts *sync.Map, streamSettings *internet.MemoryStreamConfig, stall time.Duration, maxConns int) *http.Client {
|
|
||||||
var (
|
var (
|
||||||
tlsConfig *tls.Config
|
tlsConfig *tls.Config
|
||||||
|
realityConfig *reality.Config
|
||||||
sockopt *internet.SocketConfig
|
sockopt *internet.SocketConfig
|
||||||
fronting *net.Destination
|
fronting *net.Destination
|
||||||
keepAlive = net.ChromeH2KeepAlivePeriod
|
|
||||||
// h3 keeps alive only when asked to, see below
|
|
||||||
h3KeepAlive time.Duration
|
|
||||||
)
|
)
|
||||||
if streamSettings != nil {
|
if streamSettings != nil {
|
||||||
tlsConfig = tls.ConfigFromStreamSettings(streamSettings)
|
tlsConfig = tls.ConfigFromStreamSettings(streamSettings)
|
||||||
|
realityConfig = reality.ConfigFromStreamSettings(streamSettings)
|
||||||
sockopt = streamSettings.SocketSettings
|
sockopt = streamSettings.SocketSettings
|
||||||
fronting = streamSettings.Destination
|
fronting = streamSettings.Destination
|
||||||
if config, ok := streamSettings.ProtocolSettings.(*Config); ok && config.Xmux != nil {
|
|
||||||
switch period := config.Xmux.HKeepAlivePeriod; {
|
|
||||||
case period > 0:
|
|
||||||
keepAlive = time.Duration(period) * time.Second
|
|
||||||
h3KeepAlive = keepAlive
|
|
||||||
case period < 0:
|
|
||||||
keepAlive = 0
|
|
||||||
}
|
}
|
||||||
}
|
overHTTP2 := allowsHTTP2(tlsConfig, realityConfig)
|
||||||
}
|
|
||||||
if tlsConfig == nil {
|
|
||||||
tlsConfig = &tls.Config{}
|
|
||||||
}
|
|
||||||
fingerprint := tls.GetFingerprint(tlsConfig.Fingerprint)
|
|
||||||
|
|
||||||
// fronting takes only the address and port, the network stays that of the
|
dial := func(ctx context.Context, addr string) (net.Conn, net.Destination, error) {
|
||||||
// dial, which is UDP for h3 though the destination is built as TCP
|
host, err := net.ParseDestination("tcp:" + addr)
|
||||||
dial := func(ctx context.Context, network, addr string) (net.Conn, net.Destination, error) {
|
|
||||||
host, err := net.ParseDestination(network + ":" + addr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, host, errors.New("bad address: ", addr).Base(err)
|
return nil, host, errors.New("bad address: ", addr).Base(err)
|
||||||
}
|
}
|
||||||
@@ -166,146 +56,74 @@ func newServiceClient(connCtx context.Context, h1Hosts *sync.Map, streamSettings
|
|||||||
}
|
}
|
||||||
|
|
||||||
var conn net.Conn
|
var conn net.Conn
|
||||||
switch {
|
if streamSettings.FinalMask != nil {
|
||||||
case streamSettings.FinalMask == nil:
|
|
||||||
conn, err = internet.DialSystem(ctx, target, sockopt)
|
|
||||||
case target.Network == net.Network_UDP:
|
|
||||||
conn, err = streamSettings.FinalMask.DialUDP(ctx, target)
|
|
||||||
default:
|
|
||||||
conn, err = streamSettings.FinalMask.DialTCP(ctx, target)
|
conn, err = streamSettings.FinalMask.DialTCP(ctx, target)
|
||||||
|
} else {
|
||||||
|
conn, err = internet.DialSystem(ctx, target, sockopt)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, host, errors.New("failed to dial to dest").Base(err)
|
return nil, host, errors.New("failed to dial to dest").Base(err)
|
||||||
}
|
}
|
||||||
// h3 closes its QUIC connection instead, as the server never hears of a UDP
|
|
||||||
// socket that closes
|
|
||||||
if network == "udp" {
|
|
||||||
return conn, host, nil
|
return conn, host, nil
|
||||||
}
|
}
|
||||||
// CloseIdleConnections leaves an h2 connection open while a stream just
|
|
||||||
// canceled has not left it yet
|
|
||||||
stop := context.AfterFunc(connCtx, func() { conn.Close() })
|
|
||||||
return &ctxConn{Conn: conn, stop: stop}, host, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
dialPlain := func(ctx context.Context, network, addr string) (net.Conn, error) {
|
dialPlain := func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
conn, _, err := dial(ctx, "tcp", addr)
|
conn, _, err := dial(ctx, addr)
|
||||||
return conn, err
|
return conn, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// http.Transport picks h2 or h1 by the ALPN in the ConnectionState of the conn
|
dialTLS := func(ctx context.Context, addr string) (net.Conn, error) {
|
||||||
dialTLS := func(ctx context.Context, network, addr string) (net.Conn, error) {
|
conn, host, err := dial(ctx, addr)
|
||||||
conn, host, err := dial(ctx, "tcp", addr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
gotlsConfig := tlsConfig.GetTLSConfig(tls.WithDestination(host))
|
if realityConfig != nil {
|
||||||
var tlsConn net.Conn
|
return reality.UClient(conn, realityConfig, ctx, host)
|
||||||
if fingerprint == nil {
|
|
||||||
tlsConn = tls.Client(conn, gotlsConfig)
|
|
||||||
} else {
|
|
||||||
tlsConn = tls.UClient(conn, gotlsConfig, fingerprint)
|
|
||||||
}
|
}
|
||||||
// the dial outlives the request that started it, and a handshake that hangs
|
|
||||||
// would keep the one connection of the host to itself
|
gotlsConfig := &gotls.Config{ServerName: host.Address.String()}
|
||||||
ctx, cancel := context.WithTimeout(ctx, stall)
|
if tlsConfig != nil {
|
||||||
defer cancel()
|
gotlsConfig = tlsConfig.GetTLSConfig(tls.WithDestination(host))
|
||||||
if err := tlsConn.(tls.Interface).HandshakeContext(ctx); err != nil {
|
}
|
||||||
|
if len(gotlsConfig.NextProtos) != 1 {
|
||||||
|
if overHTTP2 {
|
||||||
|
gotlsConfig.NextProtos = []string{"h2"}
|
||||||
|
} else {
|
||||||
|
gotlsConfig.NextProtos = []string{"http/1.1"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tlsConfig != nil {
|
||||||
|
if fingerprint := tls.GetFingerprint(tlsConfig.Fingerprint); fingerprint != nil {
|
||||||
|
uconn := tls.UClient(conn, gotlsConfig, fingerprint)
|
||||||
|
if err := uconn.(*tls.UConn).HandshakeContext(ctx); err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return tlsConn, nil
|
return uconn, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tls.Client(conn, gotlsConfig), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
secure := func(connsPerHost int) *http.Transport {
|
var secure http.RoundTripper
|
||||||
return &http.Transport{
|
if overHTTP2 {
|
||||||
DialTLSContext: dialTLS,
|
secure = &http2.Transport{
|
||||||
ForceAttemptHTTP2: true,
|
DialTLSContext: func(ctx context.Context, network, addr string, cfg *gotls.Config) (net.Conn, error) {
|
||||||
|
return dialTLS(ctx, addr)
|
||||||
|
},
|
||||||
|
IdleConnTimeout: net.ConnIdleTimeout,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
secure = &http.Transport{
|
||||||
|
DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
return dialTLS(ctx, addr)
|
||||||
|
},
|
||||||
IdleConnTimeout: net.ConnIdleTimeout,
|
IdleConnTimeout: net.ConnIdleTimeout,
|
||||||
MaxIdleConns: maxConns,
|
MaxIdleConns: maxConns,
|
||||||
MaxIdleConnsPerHost: maxConns,
|
MaxIdleConnsPerHost: maxConns,
|
||||||
MaxConnsPerHost: connsPerHost,
|
MaxConnsPerHost: maxConns,
|
||||||
HTTP2: &http.HTTP2Config{SendPingTimeout: keepAlive},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var https http.RoundTripper = &secureTransport{h2: secure(1), h1: secure(maxConns), h1Hosts: h1Hosts}
|
|
||||||
// no server offers h3 by ALPN over TCP, so it is used only when asked for alone
|
|
||||||
if slices.Equal(tlsConfig.NextProtocol, []string{"h3"}) {
|
|
||||||
quicParams := streamSettings.QuicParams
|
|
||||||
if quicParams == nil {
|
|
||||||
quicParams = &internet.QuicParams{BbrProfile: string(bbr.ProfileStandard)}
|
|
||||||
}
|
|
||||||
// the defaults are those of quic-go, with no keepalive unless asked for, as
|
|
||||||
// in DoH: http3 never closes a connection that is idle or that it dropped
|
|
||||||
// after an error, so only the idle timeout or the end of connCtx ends it
|
|
||||||
quicConfig := &quic.Config{
|
|
||||||
InitialStreamReceiveWindow: quicParams.InitStreamReceiveWindow,
|
|
||||||
MaxStreamReceiveWindow: quicParams.MaxStreamReceiveWindow,
|
|
||||||
InitialConnectionReceiveWindow: quicParams.InitConnReceiveWindow,
|
|
||||||
MaxConnectionReceiveWindow: quicParams.MaxConnReceiveWindow,
|
|
||||||
MaxIdleTimeout: time.Duration(quicParams.MaxIdleTimeout) * time.Second,
|
|
||||||
KeepAlivePeriod: time.Duration(quicParams.KeepAlivePeriod) * time.Second,
|
|
||||||
MaxIncomingStreams: quicParams.MaxIncomingStreams,
|
|
||||||
DisablePathMTUDiscovery: quicParams.DisablePathMtuDiscovery || (runtime.GOOS != "linux" && runtime.GOOS != "windows" && runtime.GOOS != "darwin"),
|
|
||||||
ChromeParrot: !quicParams.DisableChromeParrot,
|
|
||||||
}
|
|
||||||
if quicConfig.KeepAlivePeriod == 0 {
|
|
||||||
quicConfig.KeepAlivePeriod = h3KeepAlive
|
|
||||||
}
|
|
||||||
|
|
||||||
https = &http3.Transport{
|
|
||||||
QUICConfig: quicConfig,
|
|
||||||
Dial: func(ctx context.Context, addr string, _ *gotls.Config, cfg *quic.Config) (*quic.Conn, error) {
|
|
||||||
switch quicParams.Congestion {
|
|
||||||
case "", "bbr", "reno":
|
|
||||||
default:
|
|
||||||
// brutal is for Hysteria servers, not services, and a panic here, in
|
|
||||||
// a goroutine of http3, would end the process
|
|
||||||
return nil, errors.New("unsupported congestion control over h3: ", quicParams.Congestion)
|
|
||||||
}
|
|
||||||
|
|
||||||
conn, host, err := dial(ctx, "udp", addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var pktConn net.PacketConn
|
|
||||||
var udpAddr net.Addr
|
|
||||||
switch c := conn.(type) {
|
|
||||||
case *net.PacketConnWrapper:
|
|
||||||
pktConn = c.PacketConn
|
|
||||||
udpAddr = c.RemoteAddr()
|
|
||||||
case *cnc.Connection:
|
|
||||||
pktConn = &internet.FakePacketConn{Conn: c}
|
|
||||||
udpAddr = &net.UDPAddr{IP: []byte{0, 0, 0, 0}}
|
|
||||||
default:
|
|
||||||
conn.Close()
|
|
||||||
return nil, errors.New("failed to dial to dest: unexpected ", reflect.TypeOf(c))
|
|
||||||
}
|
|
||||||
|
|
||||||
// built for each dial with the host as SNI, as for h2: http3 sets the
|
|
||||||
// SNI on a copy, which a pinned CA and the ECH lookup do not see
|
|
||||||
gotlsConfig := tlsConfig.GetTLSConfig(tls.WithDestination(host))
|
|
||||||
// it is for servers, and ChromeParrot refuses a config that has it
|
|
||||||
gotlsConfig.GetCertificate = nil
|
|
||||||
tr := &quic.Transport{Conn: pktConn, DisableGSO: quicParams.DisableGSO}
|
|
||||||
if !quicParams.DisableChromeParrot {
|
|
||||||
tr.ConnectionIDGenerator = quic.ZeroLengthConnectionIDGenerator{}
|
|
||||||
}
|
|
||||||
qconn, err := tr.Dial(ctx, udpAddr, gotlsConfig, cfg)
|
|
||||||
if err != nil {
|
|
||||||
tr.Close()
|
|
||||||
pktConn.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
stop := context.AfterFunc(connCtx, func() { qconn.CloseWithError(quic.ApplicationErrorCode(http3.ErrCodeNoError), "") })
|
|
||||||
context.AfterFunc(qconn.Context(), func() { stop(); tr.Close(); pktConn.Close() })
|
|
||||||
if quicParams.Congestion != "reno" {
|
|
||||||
congestion.UseBBR(qconn, bbr.Profile(quicParams.BbrProfile))
|
|
||||||
}
|
|
||||||
return qconn, nil
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,165 +136,18 @@ func newServiceClient(connCtx context.Context, h1Hosts *sync.Map, streamSettings
|
|||||||
MaxIdleConnsPerHost: maxConns,
|
MaxIdleConnsPerHost: maxConns,
|
||||||
MaxConnsPerHost: maxConns,
|
MaxConnsPerHost: maxConns,
|
||||||
},
|
},
|
||||||
secure: https,
|
secure: secure,
|
||||||
stall: stall,
|
|
||||||
},
|
},
|
||||||
|
Timeout: timeout,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ctxConn is a connection that connCtx no longer closes once it is closed,
|
func allowsHTTP2(tlsConfig *tls.Config, realityConfig *reality.Config) bool {
|
||||||
// so that one that ends on its own leaves nothing behind in connCtx
|
if realityConfig != nil {
|
||||||
type ctxConn struct {
|
return true
|
||||||
net.Conn
|
|
||||||
stop func() bool
|
|
||||||
}
|
}
|
||||||
|
if tlsConfig == nil {
|
||||||
func (c *ctxConn) Close() error {
|
return false
|
||||||
c.stop()
|
|
||||||
return c.Conn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// secureTransport keeps a host to one connection, all that h2 needs, as without
|
|
||||||
// a limit parallel requests would each dial their own. A host that answers over
|
|
||||||
// h1 moves to a transport with more, since h1 carries one request at a time
|
|
||||||
type secureTransport struct {
|
|
||||||
h2, h1 http.RoundTripper
|
|
||||||
h1Hosts *sync.Map
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *secureTransport) RoundTrip(r *http.Request) (*http.Response, error) {
|
|
||||||
if _, ok := t.h1Hosts.Load(r.URL.Host); ok {
|
|
||||||
return t.h1.RoundTrip(r)
|
|
||||||
}
|
|
||||||
resp, err := t.h2.RoundTrip(r)
|
|
||||||
if err == nil && resp.ProtoMajor == 1 {
|
|
||||||
t.h1Hosts.Store(r.URL.Host, true)
|
|
||||||
}
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var errSessionClosed = errors.New("the session is closed")
|
|
||||||
|
|
||||||
// connPool hands the sessions of a storage the connections of XMUX, each a
|
|
||||||
// client of its own made by newClient
|
|
||||||
type connPool struct {
|
|
||||||
mu sync.Mutex
|
|
||||||
xmux *splithttp.XmuxManager
|
|
||||||
}
|
|
||||||
|
|
||||||
func newConnPool(config *splithttp.XmuxConfig, newClient func(connCtx context.Context) *http.Client) *connPool {
|
|
||||||
var xmuxConfig splithttp.XmuxConfig
|
|
||||||
if config != nil {
|
|
||||||
xmuxConfig = *config
|
|
||||||
}
|
|
||||||
return &connPool{xmux: splithttp.NewXmuxManager(xmuxConfig, func() splithttp.XmuxConn {
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
return &poolConn{next: newClient(ctx).Transport, cancel: cancel}
|
|
||||||
})}
|
|
||||||
}
|
|
||||||
|
|
||||||
// poolConn is a connection of XMUX, which closes once XMUX drops it and the
|
|
||||||
// requests on it end
|
|
||||||
type poolConn struct {
|
|
||||||
next http.RoundTripper
|
|
||||||
cancel context.CancelFunc
|
|
||||||
|
|
||||||
mu sync.Mutex
|
|
||||||
inflight int
|
|
||||||
closing bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *poolConn) IsClosed() bool {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
return c.closing
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *poolConn) Close() error {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
c.closing = true
|
|
||||||
if c.inflight == 0 {
|
|
||||||
c.cancel()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *poolConn) done() {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
c.inflight--
|
|
||||||
if c.closing && c.inflight == 0 {
|
|
||||||
c.cancel()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// roundTrip sends a request counted in inflight by the caller
|
|
||||||
func (c *poolConn) roundTrip(r *http.Request) (*http.Response, error) {
|
|
||||||
resp, err := c.next.RoundTrip(r)
|
|
||||||
if err != nil {
|
|
||||||
c.done()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
resp.Body = &doneBody{ReadCloser: resp.Body, done: sync.OnceFunc(c.done)}
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type doneBody struct {
|
|
||||||
io.ReadCloser
|
|
||||||
done func()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *doneBody) Close() error {
|
|
||||||
err := b.ReadCloser.Close()
|
|
||||||
b.done()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// sessionTransport sends the requests of one session over a connection of
|
|
||||||
// XMUX, and over another once that one has no requests or time left
|
|
||||||
type sessionTransport struct {
|
|
||||||
pool *connPool
|
|
||||||
|
|
||||||
mu sync.Mutex
|
|
||||||
client *splithttp.XmuxClient
|
|
||||||
closed bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *sessionTransport) RoundTrip(r *http.Request) (*http.Response, error) {
|
|
||||||
t.mu.Lock()
|
|
||||||
if t.closed {
|
|
||||||
t.mu.Unlock()
|
|
||||||
return nil, errSessionClosed
|
|
||||||
}
|
|
||||||
client := t.client
|
|
||||||
if client == nil || client.LeftRequests.Add(-1) < 0 || !client.UnreusableAt.IsZero() && time.Now().After(client.UnreusableAt) {
|
|
||||||
t.pool.mu.Lock()
|
|
||||||
t.client = t.pool.xmux.GetXmuxClient(r.Context())
|
|
||||||
t.pool.mu.Unlock()
|
|
||||||
t.client.AddRunning()
|
|
||||||
t.client.LeftRequests.Add(-1)
|
|
||||||
if client != nil {
|
|
||||||
client.DoneRunning()
|
|
||||||
}
|
|
||||||
client = t.client
|
|
||||||
}
|
|
||||||
// counted under mu, or another request of the session that moves on could
|
|
||||||
// close the connection before this one is sent
|
|
||||||
conn := client.XmuxConn.(*poolConn)
|
|
||||||
conn.mu.Lock()
|
|
||||||
conn.inflight++
|
|
||||||
conn.mu.Unlock()
|
|
||||||
t.mu.Unlock()
|
|
||||||
return conn.roundTrip(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *sessionTransport) Close() {
|
|
||||||
t.mu.Lock()
|
|
||||||
defer t.mu.Unlock()
|
|
||||||
t.closed = true
|
|
||||||
if t.client != nil {
|
|
||||||
t.client.DoneRunning()
|
|
||||||
t.client = nil
|
|
||||||
}
|
}
|
||||||
|
return len(tlsConfig.NextProtocol) == 1 && tlsConfig.NextProtocol[0] == "h2"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +1,16 @@
|
|||||||
package xdrive
|
package xdrive
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
gotls "crypto/tls"
|
gotls "crypto/tls"
|
||||||
"crypto/x509"
|
|
||||||
"encoding/base64"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
|
||||||
"slices"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/apernet/quic-go"
|
|
||||||
"github.com/apernet/quic-go/http3"
|
|
||||||
xnet "github.com/xtls/xray-core/common/net"
|
xnet "github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/net/cnc"
|
|
||||||
"github.com/xtls/xray-core/common/protocol/tls/cert"
|
|
||||||
"github.com/xtls/xray-core/common/utils"
|
|
||||||
"github.com/xtls/xray-core/transport/internet"
|
"github.com/xtls/xray-core/transport/internet"
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask"
|
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask/noise"
|
|
||||||
"github.com/xtls/xray-core/transport/internet/splithttp"
|
|
||||||
"github.com/xtls/xray-core/transport/internet/tls"
|
"github.com/xtls/xray-core/transport/internet/tls"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -85,7 +68,7 @@ func sniForSettings(t *testing.T, serverName string) string {
|
|||||||
driveFilesURL = "https://www.googleapis.com/drive/v3/files"
|
driveFilesURL = "https://www.googleapis.com/drive/v3/files"
|
||||||
defer func() { driveFilesURL = prev }()
|
defer func() { driveFilesURL = prev }()
|
||||||
|
|
||||||
client := newServiceClient(context.Background(), new(sync.Map), settings, 5*time.Second, 8)
|
client := newServiceClient(settings, 5*time.Second, 8)
|
||||||
req, err := http.NewRequest(http.MethodGet, driveFilesURL, nil)
|
req, err := http.NewRequest(http.MethodGet, driveFilesURL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("request: %v", err)
|
t.Fatalf("request: %v", err)
|
||||||
@@ -116,874 +99,3 @@ func TestServiceSNIOverride(t *testing.T) {
|
|||||||
t.Fatalf("explicit serverName gave SNI %q, want www.google.com", got)
|
t.Fatalf("explicit serverName gave SNI %q, want www.google.com", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// slowReader hands out one byte every gap, the way a body crosses a slow link
|
|
||||||
type slowReader struct {
|
|
||||||
left int
|
|
||||||
gap time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *slowReader) Read(p []byte) (int, error) {
|
|
||||||
if r.left == 0 {
|
|
||||||
return 0, io.EOF
|
|
||||||
}
|
|
||||||
time.Sleep(r.gap)
|
|
||||||
r.left--
|
|
||||||
p[0] = 'x'
|
|
||||||
return 1, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// a request that keeps moving outlives the stall limit many times over, one
|
|
||||||
// that stands still does not get much past it
|
|
||||||
func TestServiceClientEndsOnlyStalledRequests(t *testing.T) {
|
|
||||||
const stall = 200 * time.Millisecond
|
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
io.Copy(io.Discard, r.Body)
|
|
||||||
switch r.URL.Path {
|
|
||||||
case "/slow":
|
|
||||||
for i := 0; i < 20; i++ {
|
|
||||||
w.Write([]byte("x"))
|
|
||||||
w.(http.Flusher).Flush()
|
|
||||||
time.Sleep(stall / 4)
|
|
||||||
}
|
|
||||||
case "/stuck":
|
|
||||||
w.Write([]byte("x"))
|
|
||||||
w.(http.Flusher).Flush()
|
|
||||||
time.Sleep(5 * stall)
|
|
||||||
case "/silent":
|
|
||||||
time.Sleep(5 * stall)
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
defer server.Close()
|
|
||||||
client := newServiceClient(context.Background(), new(sync.Map), &internet.MemoryStreamConfig{}, stall, 8)
|
|
||||||
|
|
||||||
fetch := func(path string, body io.Reader) (int, error) {
|
|
||||||
req, err := http.NewRequest(http.MethodPost, server.URL+path, body)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
got, err := io.ReadAll(resp.Body)
|
|
||||||
return len(got), err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := fetch("/", &slowReader{left: 20, gap: stall / 4}); err != nil {
|
|
||||||
t.Errorf("a slow upload failed: %v", err)
|
|
||||||
}
|
|
||||||
if n, err := fetch("/slow", nil); err != nil || n != 20 {
|
|
||||||
t.Errorf("a slow download gave %d bytes, %v", n, err)
|
|
||||||
}
|
|
||||||
for _, path := range []string{"/stuck", "/silent"} {
|
|
||||||
start := time.Now()
|
|
||||||
_, err := fetch(path, nil)
|
|
||||||
if !errors.Is(err, errStalled) {
|
|
||||||
t.Errorf("%s gave %v, want %v", path, err, errStalled)
|
|
||||||
}
|
|
||||||
if took := time.Since(start); took > 4*stall {
|
|
||||||
t.Errorf("%s took %v to give up", path, took)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// a body that the transport sends again by itself keeps the request going too
|
|
||||||
replay := &serviceTransport{stall: stall, plain: roundTrip(func(r *http.Request) (*http.Response, error) {
|
|
||||||
body, err := r.GetBody()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for b := make([]byte, 1); ; time.Sleep(stall / 4) {
|
|
||||||
if _, err := body.Read(b); err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := context.Cause(r.Context()); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody}, nil
|
|
||||||
})}
|
|
||||||
req, err := http.NewRequest(http.MethodPost, "http://drive/", bytes.NewReader(make([]byte, 20)))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if resp, err := replay.RoundTrip(req); err != nil {
|
|
||||||
t.Errorf("a body sent again failed: %v", err)
|
|
||||||
} else {
|
|
||||||
resp.Body.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// over TLS a request that stands still ends the same way, and says so, on h2
|
|
||||||
// as on h1
|
|
||||||
func TestServiceClientStallOverTLS(t *testing.T) {
|
|
||||||
const stall = 200 * time.Millisecond
|
|
||||||
for _, protos := range [][]string{chromeALPN, {"http/1.1"}} {
|
|
||||||
server := newTLSServer(t, &gotls.Config{NextProtos: protos}, func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.URL.Path == "/stuck" {
|
|
||||||
w.Write([]byte("x"))
|
|
||||||
w.(http.Flusher).Flush()
|
|
||||||
}
|
|
||||||
time.Sleep(5 * stall)
|
|
||||||
})
|
|
||||||
client := newServiceClient(context.Background(), new(sync.Map), server.settings(&tls.Config{}), stall, 8)
|
|
||||||
|
|
||||||
for _, path := range []string{"/stuck", "/silent"} {
|
|
||||||
start := time.Now()
|
|
||||||
resp, err := client.Get(server.URL + path)
|
|
||||||
if err == nil {
|
|
||||||
if resp.Proto != picked(protos) {
|
|
||||||
t.Fatalf("%s went over %s", path, resp.Proto)
|
|
||||||
}
|
|
||||||
_, err = io.ReadAll(resp.Body)
|
|
||||||
resp.Body.Close()
|
|
||||||
}
|
|
||||||
if !errors.Is(err, errStalled) {
|
|
||||||
t.Errorf("server %v: %s gave %v, want %v", protos, path, err, errStalled)
|
|
||||||
}
|
|
||||||
if took := time.Since(start); took > 4*stall {
|
|
||||||
t.Errorf("server %v: %s took %v to give up", protos, path, took)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// tlsServer is a TLS server that keeps the last hello it got and counts its
|
|
||||||
// connections, and those closed
|
|
||||||
type tlsServer struct {
|
|
||||||
*httptest.Server
|
|
||||||
mu sync.Mutex
|
|
||||||
hello *gotls.ClientHelloInfo
|
|
||||||
conns atomic.Int32
|
|
||||||
closed atomic.Int32
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTLSServer(t *testing.T, config *gotls.Config, handler http.HandlerFunc) *tlsServer {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
s := &tlsServer{Server: httptest.NewUnstartedServer(handler)}
|
|
||||||
config.GetConfigForClient = func(hello *gotls.ClientHelloInfo) (*gotls.Config, error) {
|
|
||||||
s.mu.Lock()
|
|
||||||
s.hello = hello
|
|
||||||
s.mu.Unlock()
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
s.TLS = config
|
|
||||||
s.Config.ConnState = func(_ net.Conn, state http.ConnState) {
|
|
||||||
switch state {
|
|
||||||
case http.StateNew:
|
|
||||||
s.conns.Add(1)
|
|
||||||
case http.StateClosed:
|
|
||||||
s.closed.Add(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.StartTLS()
|
|
||||||
t.Cleanup(s.Close)
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *tlsServer) lastHello() *gotls.ClientHelloInfo {
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
return s.hello
|
|
||||||
}
|
|
||||||
|
|
||||||
// settings pins the certificate of the server in config
|
|
||||||
func (s *tlsServer) settings(config *tls.Config) *internet.MemoryStreamConfig {
|
|
||||||
config.PinnedPeerCertSha256 = [][]byte{tls.GenerateCertHash(s.Certificate())}
|
|
||||||
return &internet.MemoryStreamConfig{ProtocolName: protocolName, SecuritySettings: config}
|
|
||||||
}
|
|
||||||
|
|
||||||
// picked is the protocol a server with ALPN protos answers a Chrome hello with
|
|
||||||
func picked(protos []string) string {
|
|
||||||
if protos[0] == "h2" {
|
|
||||||
return "HTTP/2.0"
|
|
||||||
}
|
|
||||||
return "HTTP/1.1"
|
|
||||||
}
|
|
||||||
|
|
||||||
var chromeALPN = []string{"h2", "http/1.1"}
|
|
||||||
|
|
||||||
// the hello offers the ALPN of the fingerprint whatever "alpn" says, or with Go
|
|
||||||
// TLS that of the config, h2 and http/1.1 by default, and the client speaks what
|
|
||||||
// the server picks from it
|
|
||||||
func TestServiceClientSpeaksWhatServerPicks(t *testing.T) {
|
|
||||||
for _, protos := range [][]string{chromeALPN, {"http/1.1"}} {
|
|
||||||
server := newTLSServer(t, &gotls.Config{NextProtos: protos}, func(w http.ResponseWriter, r *http.Request) {})
|
|
||||||
for _, config := range []*tls.Config{{}, {NextProtocol: []string{"h2"}}, {NextProtocol: []string{"http/1.1"}}, {Fingerprint: "unsafe"}} {
|
|
||||||
client := newServiceClient(context.Background(), new(sync.Map), server.settings(config), 5*time.Second, 8)
|
|
||||||
resp, err := client.Get(server.URL)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("server %v, alpn %v, fingerprint %q: %v", protos, config.NextProtocol, config.Fingerprint, err)
|
|
||||||
}
|
|
||||||
resp.Body.Close()
|
|
||||||
if resp.Proto != picked(protos) {
|
|
||||||
t.Errorf("server %v, alpn %v, fingerprint %q: went over %s", protos, config.NextProtocol, config.Fingerprint, resp.Proto)
|
|
||||||
}
|
|
||||||
if offered := server.lastHello().SupportedProtos; !slices.Equal(offered, chromeALPN) {
|
|
||||||
t.Errorf("server %v, alpn %v, fingerprint %q: the hello offered %v, want %v", protos, config.NextProtocol, config.Fingerprint, offered, chromeALPN)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// with no tlsSettings the hello is that of Chrome too, which unlike Go TLS
|
|
||||||
// offers certificate compression
|
|
||||||
func TestServiceClientDefaultsToChrome(t *testing.T) {
|
|
||||||
server := newTLSServer(t, &gotls.Config{NextProtos: chromeALPN}, func(w http.ResponseWriter, r *http.Request) {})
|
|
||||||
client := newServiceClient(context.Background(), new(sync.Map), &internet.MemoryStreamConfig{}, 5*time.Second, 8)
|
|
||||||
// the test certificate is not trusted, the hello is all that is needed
|
|
||||||
if resp, err := client.Get(server.URL); err == nil {
|
|
||||||
resp.Body.Close()
|
|
||||||
}
|
|
||||||
hello := server.lastHello()
|
|
||||||
if hello == nil {
|
|
||||||
t.Fatal("no hello")
|
|
||||||
}
|
|
||||||
if !slices.Equal(hello.SupportedProtos, chromeALPN) || !slices.Contains(hello.Extensions, 27) {
|
|
||||||
t.Errorf("the hello offered %v with extensions %v, not like Chrome", hello.SupportedProtos, hello.Extensions)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// a request says it is chrome, as the hello does, unless it says what it is
|
|
||||||
func TestServiceClientUserAgent(t *testing.T) {
|
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
io.WriteString(w, r.UserAgent())
|
|
||||||
}))
|
|
||||||
defer server.Close()
|
|
||||||
client := newServiceClient(context.Background(), new(sync.Map), &internet.MemoryStreamConfig{}, 5*time.Second, 8)
|
|
||||||
for ua, want := range map[string]string{"": utils.ChromeUA, "firefox": utils.FirefoxUA, "custom/1.0": "custom/1.0"} {
|
|
||||||
req, _ := http.NewRequest(http.MethodGet, server.URL, nil)
|
|
||||||
if ua != "" {
|
|
||||||
req.Header.Set("User-Agent", ua)
|
|
||||||
}
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
got, _ := io.ReadAll(resp.Body)
|
|
||||||
resp.Body.Close()
|
|
||||||
if string(got) != want {
|
|
||||||
t.Errorf("with %q the server got %q, not %q", ua, got, want)
|
|
||||||
}
|
|
||||||
if req.Header.Get("User-Agent") != ua || len(req.Header) > min(len(ua), 1) {
|
|
||||||
t.Errorf("with %q the request was changed to %v", ua, req.Header)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parallel requests share one h2 connection instead of dialing one each, the
|
|
||||||
// first ones as the later ones, and next to a host that answers over h1
|
|
||||||
func TestServiceClientOneConnection(t *testing.T) {
|
|
||||||
server := newTLSServer(t, &gotls.Config{NextProtos: chromeALPN}, func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
time.Sleep(50 * time.Millisecond)
|
|
||||||
})
|
|
||||||
h1 := newTLSServer(t, &gotls.Config{NextProtos: []string{"http/1.1"}}, func(w http.ResponseWriter, r *http.Request) {})
|
|
||||||
client := newServiceClient(context.Background(), new(sync.Map), server.settings(&tls.Config{}), 5*time.Second, driveMaxInflight)
|
|
||||||
get := func(url string) {
|
|
||||||
resp, err := client.Get(url)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
resp.Body.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range 2 {
|
|
||||||
if i == 1 {
|
|
||||||
get(h1.URL)
|
|
||||||
}
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
for range driveMaxInflight {
|
|
||||||
wg.Go(func() { get(server.URL) })
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
if n := server.conns.Load(); n != 1 {
|
|
||||||
t.Errorf("round %d: %d parallel requests took %d connections", i, driveMaxInflight, n)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// in the format of xray tls ech, for public name public.example
|
|
||||||
const (
|
|
||||||
testECHConfigList = "AEH+DQA9AAAgACBziE85FLxJySxZAPRHs8CDCbuz1h76I9pLhctC/kgScAAEAAEAAQAOcHVibGljLmV4YW1wbGUAAA=="
|
|
||||||
testECHServerKeys = "ACD2AjC5yuBQ3430geNDLdIRndloCry/DBb9ZeYp0TmeowBB/g0APQAAIAAgc4hPORS8ScksWQD0R7PAgwm7s9Ye+iPaS4XLQv5IEnAABAABAAEADnB1YmxpYy5leGFtcGxlAAA="
|
|
||||||
)
|
|
||||||
|
|
||||||
// a server that speaks only h1, or gets ALPN http/1.1 inside ECH or from Go TLS,
|
|
||||||
// answers over h1, and after that answer parallel requests get more than one
|
|
||||||
// connection
|
|
||||||
func TestServiceClientHTTP1(t *testing.T) {
|
|
||||||
keys, err := base64.StdEncoding.DecodeString(testECHServerKeys)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
echKeys, err := tls.ConvertToGoECHKeys(keys)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
ech := &tls.Config{ServerName: "example.com", NextProtocol: []string{"http/1.1"}, EchConfigList: testECHConfigList}
|
|
||||||
|
|
||||||
for _, c := range []struct {
|
|
||||||
protos, seen []string
|
|
||||||
config *tls.Config
|
|
||||||
}{
|
|
||||||
{[]string{"http/1.1"}, chromeALPN, &tls.Config{}},
|
|
||||||
{chromeALPN, []string{"http/1.1"}, ech},
|
|
||||||
{chromeALPN, []string{"http/1.1"}, &tls.Config{NextProtocol: []string{"http/1.1"}, Fingerprint: "unsafe"}},
|
|
||||||
} {
|
|
||||||
server := newTLSServer(t, &gotls.Config{NextProtos: c.protos, EncryptedClientHelloKeys: echKeys}, func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
time.Sleep(50 * time.Millisecond)
|
|
||||||
})
|
|
||||||
client := newServiceClient(context.Background(), new(sync.Map), server.settings(c.config), 5*time.Second, 8)
|
|
||||||
get := func() {
|
|
||||||
resp, err := client.Get(server.URL)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
resp.Body.Close()
|
|
||||||
if resp.Proto != "HTTP/1.1" || resp.TLS.ECHAccepted != (c.config.EchConfigList != "") {
|
|
||||||
t.Errorf("server %v, ALPN %v: went over %s, ECH accepted %v", c.protos, c.seen, resp.Proto, resp.TLS.ECHAccepted)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get()
|
|
||||||
first := server.conns.Load()
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
for range 4 {
|
|
||||||
wg.Go(get)
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
if offered := server.lastHello().SupportedProtos; !slices.Equal(offered, c.seen) {
|
|
||||||
t.Errorf("server %v: saw ALPN %v, want %v", c.protos, offered, c.seen)
|
|
||||||
}
|
|
||||||
if n := server.conns.Load() - first; n < 2 {
|
|
||||||
t.Errorf("server %v, ALPN %v: 4 parallel requests over h1 took %d connections", c.protos, c.seen, n)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// without the keys the server gets the outer hello, which offers the ALPN of
|
|
||||||
// Chrome
|
|
||||||
outer := newTLSServer(t, &gotls.Config{NextProtos: chromeALPN}, func(w http.ResponseWriter, r *http.Request) {})
|
|
||||||
if resp, err := newServiceClient(context.Background(), new(sync.Map), outer.settings(ech), 5*time.Second, 8).Get(outer.URL); err == nil {
|
|
||||||
resp.Body.Close()
|
|
||||||
}
|
|
||||||
if hello := outer.lastHello(); hello == nil {
|
|
||||||
t.Error("no outer hello")
|
|
||||||
} else if !slices.Equal(hello.SupportedProtos, chromeALPN) {
|
|
||||||
t.Errorf("the outer hello offered %v, want %v", hello.SupportedProtos, chromeALPN)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// muteFirst leaves the first connection it accepts unanswered
|
|
||||||
type muteFirst struct {
|
|
||||||
net.Listener
|
|
||||||
muted net.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *muteFirst) Accept() (net.Conn, error) {
|
|
||||||
if l.muted == nil {
|
|
||||||
conn, err := l.Listener.Accept()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
l.muted = conn
|
|
||||||
}
|
|
||||||
return l.Listener.Accept()
|
|
||||||
}
|
|
||||||
|
|
||||||
// a handshake that hangs ends after stall, and does not keep the host from
|
|
||||||
// getting another connection
|
|
||||||
func TestServiceClientHungHandshake(t *testing.T) {
|
|
||||||
const stall = 200 * time.Millisecond
|
|
||||||
for _, fingerprint := range []string{"", "unsafe"} {
|
|
||||||
server := &tlsServer{Server: httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))}
|
|
||||||
server.Listener = &muteFirst{Listener: server.Listener}
|
|
||||||
server.StartTLS()
|
|
||||||
t.Cleanup(server.Close)
|
|
||||||
client := newServiceClient(context.Background(), new(sync.Map), server.settings(&tls.Config{Fingerprint: fingerprint}), stall, 8)
|
|
||||||
|
|
||||||
if _, err := client.Get(server.URL); err == nil {
|
|
||||||
t.Fatalf("fingerprint %q: a request got through a handshake that hung", fingerprint)
|
|
||||||
}
|
|
||||||
for i := range 2 {
|
|
||||||
resp, err := client.Get(server.URL)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("fingerprint %q: request %d after a handshake that hung: %v", fingerprint, i, err)
|
|
||||||
}
|
|
||||||
resp.Body.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// a connection whose handshake failed is closed, not left to the garbage
|
|
||||||
// collector
|
|
||||||
func TestServiceClientClosesFailedHandshake(t *testing.T) {
|
|
||||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer ln.Close()
|
|
||||||
|
|
||||||
for _, fingerprint := range []string{"", "unsafe"} {
|
|
||||||
settings := &internet.MemoryStreamConfig{ProtocolName: protocolName, SecuritySettings: &tls.Config{Fingerprint: fingerprint}}
|
|
||||||
go newServiceClient(context.Background(), new(sync.Map), settings, 5*time.Second, 8).Get("https://" + ln.Addr().String())
|
|
||||||
conn, err := ln.Accept()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
// not TLS, so the handshake fails at once
|
|
||||||
conn.Write([]byte("HTTP/1.1 400 Bad Request\r\n\r\n"))
|
|
||||||
conn.SetReadDeadline(time.Now().Add(2 * time.Second))
|
|
||||||
if _, err := io.Copy(io.Discard, conn); err != nil {
|
|
||||||
t.Errorf("fingerprint %q: the client left the connection open: %v", fingerprint, err)
|
|
||||||
}
|
|
||||||
conn.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// afterFuncs counts the functions that context.AfterFunc registers on it and
|
|
||||||
// that are not stopped yet
|
|
||||||
type afterFuncs struct {
|
|
||||||
context.Context
|
|
||||||
live atomic.Int32
|
|
||||||
}
|
|
||||||
|
|
||||||
// Value hides the cancelCtx below, which context.AfterFunc would use instead
|
|
||||||
func (c *afterFuncs) Value(any) any { return nil }
|
|
||||||
|
|
||||||
func (c *afterFuncs) AfterFunc(func()) func() bool {
|
|
||||||
c.live.Add(1)
|
|
||||||
return func() bool {
|
|
||||||
c.live.Add(-1)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// a connection that ends on its own, here after Connection: close, leaves
|
|
||||||
// nothing on the context that closes the connections of the client
|
|
||||||
func TestServiceClientForgetsClosedConnections(t *testing.T) {
|
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Set("Connection", "close")
|
|
||||||
}))
|
|
||||||
defer server.Close()
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
defer cancel()
|
|
||||||
connCtx := &afterFuncs{Context: ctx}
|
|
||||||
client := newServiceClient(connCtx, new(sync.Map), &internet.MemoryStreamConfig{}, 5*time.Second, 8)
|
|
||||||
|
|
||||||
for range 4 {
|
|
||||||
resp, err := client.Get(server.URL)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
resp.Body.Close()
|
|
||||||
}
|
|
||||||
waitFor(t, "the closed connections to leave the context", func() bool { return connCtx.live.Load() == 0 })
|
|
||||||
}
|
|
||||||
|
|
||||||
type roundTrip func(*http.Request) (*http.Response, error)
|
|
||||||
|
|
||||||
func (f roundTrip) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) }
|
|
||||||
|
|
||||||
// "hKeepAlivePeriod" of XMUX sets when an idle h2 connection pings, -1 never,
|
|
||||||
// and h3 keeps alive only when it is set
|
|
||||||
func TestServiceClientKeepAlive(t *testing.T) {
|
|
||||||
for period, want := range map[int64]time.Duration{0: xnet.ChromeH2KeepAlivePeriod, 20: 20 * time.Second, -1: 0} {
|
|
||||||
settings := &internet.MemoryStreamConfig{ProtocolSettings: &Config{Xmux: &splithttp.XmuxConfig{HKeepAlivePeriod: period}}}
|
|
||||||
client := newServiceClient(context.Background(), new(sync.Map), settings, 5*time.Second, 8)
|
|
||||||
h2 := client.Transport.(*serviceTransport).secure.(*secureTransport).h2.(*http.Transport)
|
|
||||||
if got := h2.HTTP2.SendPingTimeout; got != want {
|
|
||||||
t.Errorf("hKeepAlivePeriod %d pings after %v, want %v", period, got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for period, want := range map[int64]time.Duration{0: 0, 20: 20 * time.Second, -1: 0} {
|
|
||||||
settings := &internet.MemoryStreamConfig{
|
|
||||||
ProtocolSettings: &Config{Xmux: &splithttp.XmuxConfig{HKeepAlivePeriod: period}},
|
|
||||||
SecuritySettings: &tls.Config{NextProtocol: []string{"h3"}},
|
|
||||||
}
|
|
||||||
client := newServiceClient(context.Background(), new(sync.Map), settings, 5*time.Second, 8)
|
|
||||||
if got := client.Transport.(*serviceTransport).secure.(*http3.Transport).QUICConfig.KeepAlivePeriod; got != want {
|
|
||||||
t.Errorf("hKeepAlivePeriod %d keeps h3 alive every %v, want %v", period, got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// h3 is used when "alpn" asks for it alone, and only then
|
|
||||||
func TestServiceClientH3Selection(t *testing.T) {
|
|
||||||
for _, c := range []struct {
|
|
||||||
alpn []string
|
|
||||||
h3 bool
|
|
||||||
}{
|
|
||||||
{nil, false},
|
|
||||||
{[]string{"h2"}, false},
|
|
||||||
{[]string{"http/1.1"}, false},
|
|
||||||
{[]string{"h3", "h2"}, false},
|
|
||||||
{[]string{"h2", "h3"}, false},
|
|
||||||
{[]string{"h3"}, true},
|
|
||||||
} {
|
|
||||||
client := newServiceClient(context.Background(), new(sync.Map), &internet.MemoryStreamConfig{SecuritySettings: &tls.Config{NextProtocol: c.alpn}}, 5*time.Second, 8)
|
|
||||||
if _, h3 := client.Transport.(*serviceTransport).secure.(*http3.Transport); h3 != c.h3 {
|
|
||||||
t.Errorf("alpn %v: h3 %v, want %v", c.alpn, h3, c.h3)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if _, h3 := newServiceClient(context.Background(), new(sync.Map), &internet.MemoryStreamConfig{}, 5*time.Second, 8).Transport.(*serviceTransport).secure.(*http3.Transport); h3 {
|
|
||||||
t.Error("h3 without tlsSettings")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// h3Server is an http3 server that keeps the SNI of each connection, and counts
|
|
||||||
// its connections and those closed
|
|
||||||
type h3Server struct {
|
|
||||||
port xnet.Port
|
|
||||||
hash, caHash [32]byte
|
|
||||||
mu sync.Mutex
|
|
||||||
snis []string
|
|
||||||
conns atomic.Int32
|
|
||||||
closed atomic.Int32
|
|
||||||
}
|
|
||||||
|
|
||||||
func newH3Server(t *testing.T, handler http.Handler) *h3Server {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
// ECDSA, as ChromeParrot offers no Ed25519, from a CA of its own
|
|
||||||
ca, caHash := cert.MustGenerate(nil, cert.Authority(true), cert.KeyUsage(x509.KeyUsageCertSign))
|
|
||||||
certificate, hash := cert.MustGenerate(ca, cert.DNSNames("www.googleapis.com", "oauth2.googleapis.com"))
|
|
||||||
key, err := x509.ParsePKCS8PrivateKey(certificate.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
conn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1)})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
s := &h3Server{port: xnet.Port(conn.LocalAddr().(*net.UDPAddr).Port), hash: hash, caHash: caHash}
|
|
||||||
server := &http3.Server{
|
|
||||||
Handler: handler,
|
|
||||||
TLSConfig: http3.ConfigureTLSConfig(&gotls.Config{
|
|
||||||
Certificates: []gotls.Certificate{{Certificate: [][]byte{certificate.Certificate, ca.Certificate}, PrivateKey: key}},
|
|
||||||
GetConfigForClient: func(hello *gotls.ClientHelloInfo) (*gotls.Config, error) {
|
|
||||||
s.mu.Lock()
|
|
||||||
s.snis = append(s.snis, hello.ServerName)
|
|
||||||
s.mu.Unlock()
|
|
||||||
return nil, nil
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
ConnContext: func(ctx context.Context, c *quic.Conn) context.Context {
|
|
||||||
s.conns.Add(1)
|
|
||||||
context.AfterFunc(c.Context(), func() { s.closed.Add(1) })
|
|
||||||
return ctx
|
|
||||||
},
|
|
||||||
}
|
|
||||||
go server.Serve(conn)
|
|
||||||
t.Cleanup(func() {
|
|
||||||
server.Close()
|
|
||||||
conn.Close()
|
|
||||||
})
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// settings sends every host to the server over h3, through a destination built
|
|
||||||
// as TCP the way conf builds it, and pins the certificate of the server in config
|
|
||||||
func (s *h3Server) settings(config *tls.Config) *internet.MemoryStreamConfig {
|
|
||||||
config.NextProtocol = []string{"h3"}
|
|
||||||
config.PinnedPeerCertSha256 = [][]byte{s.hash[:]}
|
|
||||||
return &internet.MemoryStreamConfig{
|
|
||||||
ProtocolName: protocolName,
|
|
||||||
Destination: &xnet.Destination{Address: xnet.LocalHostIP, Port: s.port, Network: xnet.Network_TCP},
|
|
||||||
SecuritySettings: config,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *h3Server) seenSNIs() []string {
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
return slices.Sorted(slices.Values(s.snis))
|
|
||||||
}
|
|
||||||
|
|
||||||
// one client serves two hosts, and each gets its own SNI unless serverName
|
|
||||||
// says otherwise, which is also the name a pinned CA checks the certificate for
|
|
||||||
func TestServiceClientH3SNI(t *testing.T) {
|
|
||||||
for _, c := range []struct {
|
|
||||||
serverName string
|
|
||||||
pinCA bool
|
|
||||||
want []string
|
|
||||||
}{
|
|
||||||
{"", false, []string{"oauth2.googleapis.com", "www.googleapis.com"}},
|
|
||||||
{"www.google.com", false, []string{"www.google.com", "www.google.com"}},
|
|
||||||
{"", true, []string{"oauth2.googleapis.com", "www.googleapis.com"}},
|
|
||||||
} {
|
|
||||||
server := newH3Server(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
|
|
||||||
settings := server.settings(&tls.Config{ServerName: c.serverName})
|
|
||||||
if c.pinCA {
|
|
||||||
settings.SecuritySettings.(*tls.Config).PinnedPeerCertSha256 = [][]byte{server.caHash[:]}
|
|
||||||
}
|
|
||||||
client := newServiceClient(context.Background(), new(sync.Map), settings, 5*time.Second, 8)
|
|
||||||
for _, url := range []string{"https://www.googleapis.com/drive/v3/files", "https://oauth2.googleapis.com/token"} {
|
|
||||||
resp, err := client.Get(url)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("serverName %q, pinned CA %v, %s: %v", c.serverName, c.pinCA, url, err)
|
|
||||||
}
|
|
||||||
resp.Body.Close()
|
|
||||||
if resp.Proto != "HTTP/3.0" {
|
|
||||||
t.Errorf("serverName %q, %s: went over %s", c.serverName, url, resp.Proto)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if snis := server.seenSNIs(); !slices.Equal(snis, c.want) {
|
|
||||||
t.Errorf("serverName %q: SNIs %v, want %v", c.serverName, snis, c.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// a request that stands still ends over h3 too, and says so
|
|
||||||
func TestServiceClientStallOverH3(t *testing.T) {
|
|
||||||
const stall = 200 * time.Millisecond
|
|
||||||
server := newH3Server(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.URL.Path == "/stuck" {
|
|
||||||
w.Write([]byte("x"))
|
|
||||||
w.(http.Flusher).Flush()
|
|
||||||
}
|
|
||||||
time.Sleep(5 * stall)
|
|
||||||
}))
|
|
||||||
client := newServiceClient(context.Background(), new(sync.Map), server.settings(&tls.Config{}), stall, 8)
|
|
||||||
|
|
||||||
for _, path := range []string{"/stuck", "/silent"} {
|
|
||||||
start := time.Now()
|
|
||||||
resp, err := client.Get("https://www.googleapis.com" + path)
|
|
||||||
if err == nil {
|
|
||||||
if resp.Proto != "HTTP/3.0" {
|
|
||||||
t.Fatalf("%s went over %s", path, resp.Proto)
|
|
||||||
}
|
|
||||||
_, err = io.ReadAll(resp.Body)
|
|
||||||
resp.Body.Close()
|
|
||||||
}
|
|
||||||
if !errors.Is(err, errStalled) {
|
|
||||||
t.Errorf("%s gave %v, want %v", path, err, errStalled)
|
|
||||||
}
|
|
||||||
if took := time.Since(start); took > 4*stall {
|
|
||||||
t.Errorf("%s took %v to give up", path, took)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// brutal and force-brutal, which are for Hysteria servers, give an error and
|
|
||||||
// no panic in a goroutine of http3, where XHTTP and DoH panic on brutal
|
|
||||||
func TestServiceClientH3Congestion(t *testing.T) {
|
|
||||||
server := newH3Server(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
|
|
||||||
for _, cc := range []string{"", "bbr", "reno", "brutal", "force-brutal"} {
|
|
||||||
settings := server.settings(&tls.Config{})
|
|
||||||
settings.QuicParams = &internet.QuicParams{Congestion: cc, BrutalUp: 1 << 20}
|
|
||||||
resp, err := newServiceClient(context.Background(), new(sync.Map), settings, 5*time.Second, 8).Get("https://www.googleapis.com/")
|
|
||||||
if err == nil {
|
|
||||||
resp.Body.Close()
|
|
||||||
}
|
|
||||||
if brutal := strings.HasSuffix(cc, "brutal"); brutal != (err != nil) || brutal && !strings.Contains(err.Error(), cc) {
|
|
||||||
t.Errorf("congestion %q gave %v", cc, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// recordConn keeps the datagrams sent through it
|
|
||||||
type recordConn struct {
|
|
||||||
net.PacketConn
|
|
||||||
mu sync.Mutex
|
|
||||||
sent [][]byte
|
|
||||||
closed atomic.Bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *recordConn) WriteTo(p []byte, addr net.Addr) (int, error) {
|
|
||||||
c.mu.Lock()
|
|
||||||
c.sent = append(c.sent, bytes.Clone(p))
|
|
||||||
c.mu.Unlock()
|
|
||||||
return c.PacketConn.WriteTo(p, addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *recordConn) Close() error {
|
|
||||||
c.closed.Store(true)
|
|
||||||
return c.PacketConn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// recorder dials UDP through recordConns and keeps them
|
|
||||||
type recorder struct {
|
|
||||||
mu sync.Mutex
|
|
||||||
conns []*recordConn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *recorder) dialUDP(ctx context.Context, dest xnet.Destination) (net.PacketConn, net.Addr, error) {
|
|
||||||
conn, err := internet.DialSystem(ctx, dest, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
rec := &recordConn{PacketConn: conn.(*xnet.PacketConnWrapper).PacketConn}
|
|
||||||
r.mu.Lock()
|
|
||||||
r.conns = append(r.conns, rec)
|
|
||||||
r.mu.Unlock()
|
|
||||||
return rec, conn.RemoteAddr(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// a connection ends with its socket once it is idle for its idle timeout, as
|
|
||||||
// keepalive is off unless asked for, and so does one whose handshake failed,
|
|
||||||
// and neither leaves anything on the context that closes the connections
|
|
||||||
func TestServiceClientH3Closes(t *testing.T) {
|
|
||||||
server := newH3Server(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
defer cancel()
|
|
||||||
for _, pin := range [][]byte{server.hash[:], make([]byte, 32)} {
|
|
||||||
rec := new(recorder)
|
|
||||||
settings := server.settings(&tls.Config{})
|
|
||||||
settings.SecuritySettings.(*tls.Config).PinnedPeerCertSha256 = [][]byte{pin}
|
|
||||||
// ChromeParrot would keep the idle timeout at 30 s
|
|
||||||
settings.QuicParams = &internet.QuicParams{MaxIdleTimeout: 1, DisableChromeParrot: true}
|
|
||||||
settings.FinalMask = finalmask.NewFinalMask(nil, nil, nil, nil, rec.dialUDP, nil)
|
|
||||||
connCtx := &afterFuncs{Context: ctx}
|
|
||||||
resp, err := newServiceClient(connCtx, new(sync.Map), settings, 5*time.Second, 8).Get("https://www.googleapis.com/")
|
|
||||||
if err == nil {
|
|
||||||
resp.Body.Close()
|
|
||||||
}
|
|
||||||
if bytes.Equal(pin, server.hash[:]) != (err == nil) {
|
|
||||||
t.Fatalf("pin %x: %v", pin[:4], err)
|
|
||||||
}
|
|
||||||
|
|
||||||
closed := func() bool {
|
|
||||||
rec.mu.Lock()
|
|
||||||
defer rec.mu.Unlock()
|
|
||||||
return len(rec.conns) == 1 && rec.conns[0].closed.Load()
|
|
||||||
}
|
|
||||||
for deadline := time.Now().Add(5 * time.Second); !closed() && time.Now().Before(deadline); {
|
|
||||||
time.Sleep(10 * time.Millisecond)
|
|
||||||
}
|
|
||||||
if !closed() {
|
|
||||||
t.Errorf("pin %x: the socket was left open", pin[:4])
|
|
||||||
}
|
|
||||||
waitFor(t, "the closed connection to leave the context", func() bool { return connCtx.live.Load() == 0 })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// cncDialer gives UDP as a stream conn, the way dialerProxy does
|
|
||||||
type cncDialer struct{}
|
|
||||||
|
|
||||||
func (cncDialer) Dial(ctx context.Context, src xnet.Address, dest xnet.Destination, sockopt *internet.SocketConfig) (net.Conn, error) {
|
|
||||||
conn, err := net.Dial("udp", dest.NetAddr())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return cnc.NewConnection(cnc.ConnectionInput(conn), cnc.ConnectionOutput(conn), cnc.ConnectionOnClose(conn)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cncDialer) DestIpAddress() net.IP { return nil }
|
|
||||||
|
|
||||||
// h3 goes over the stream conn of a dialerProxy too
|
|
||||||
func TestServiceClientH3DialerProxy(t *testing.T) {
|
|
||||||
internet.UseAlternativeSystemDialer(cncDialer{})
|
|
||||||
defer internet.UseAlternativeSystemDialer(nil)
|
|
||||||
|
|
||||||
server := newH3Server(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
|
|
||||||
resp, err := newServiceClient(context.Background(), new(sync.Map), server.settings(&tls.Config{}), 5*time.Second, 8).Get("https://www.googleapis.com/")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
resp.Body.Close()
|
|
||||||
if resp.Proto != "HTTP/3.0" {
|
|
||||||
t.Errorf("went over %s", resp.Proto)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// xdrive moves data over h3 to a fake Drive, with the noise of a UDP mask sent
|
|
||||||
// ahead of the Initial of each connection
|
|
||||||
func TestDriveOverH3(t *testing.T) {
|
|
||||||
drive := newFakeDrive(t)
|
|
||||||
server := newH3Server(t, drive.server.Config.Handler)
|
|
||||||
driveTokenURL = "https://oauth2.googleapis.com/token"
|
|
||||||
driveFilesURL = "https://www.googleapis.com/files"
|
|
||||||
driveUploadURL = "https://www.googleapis.com/upload"
|
|
||||||
|
|
||||||
rec := new(recorder)
|
|
||||||
settings := server.settings(&tls.Config{})
|
|
||||||
settings.ProtocolSettings = driveSettings().ProtocolSettings
|
|
||||||
settings.ProtocolSettings.(*Config).Xmux = &splithttp.XmuxConfig{MaxConnections: &splithttp.RangeConfig{From: 4, To: 4}}
|
|
||||||
masks := []finalmask.UDPMask{&noise.Config{Items: []*noise.Item{{Packet: []byte("noise")}}}}
|
|
||||||
settings.FinalMask = finalmask.NewFinalMask(nil, masks, nil, nil, rec.dialUDP, nil)
|
|
||||||
|
|
||||||
client, srv, cleanup := pairWith(t, settings)
|
|
||||||
defer cleanup()
|
|
||||||
|
|
||||||
if _, err := client.Write([]byte("ping")); err != nil {
|
|
||||||
t.Fatalf("client write: %v", err)
|
|
||||||
}
|
|
||||||
expectRead(t, srv, "ping")
|
|
||||||
if _, err := srv.Write([]byte("pong")); err != nil {
|
|
||||||
t.Fatalf("server write: %v", err)
|
|
||||||
}
|
|
||||||
expectRead(t, client, "pong")
|
|
||||||
|
|
||||||
payload := make([]byte, 300000)
|
|
||||||
for i := range payload {
|
|
||||||
payload[i] = byte(i % 251)
|
|
||||||
}
|
|
||||||
go client.Write(payload)
|
|
||||||
if err := srv.SetReadDeadline(time.Now().Add(testPatience)); err != nil {
|
|
||||||
t.Fatalf("SetReadDeadline: %v", err)
|
|
||||||
}
|
|
||||||
got := make([]byte, len(payload))
|
|
||||||
if _, err := io.ReadFull(srv, got); err != nil {
|
|
||||||
t.Fatalf("ReadFull: %v", err)
|
|
||||||
}
|
|
||||||
if !bytes.Equal(got, payload) {
|
|
||||||
t.Fatal("payload mismatch")
|
|
||||||
}
|
|
||||||
|
|
||||||
rec.mu.Lock()
|
|
||||||
defer rec.mu.Unlock()
|
|
||||||
// one for the listener to www.googleapis.com, one for the token to
|
|
||||||
// oauth2.googleapis.com and one of XMUX for each end of the session
|
|
||||||
if len(rec.conns) != 4 {
|
|
||||||
t.Errorf("%d connections went through the mask, want 4", len(rec.conns))
|
|
||||||
}
|
|
||||||
for i, conn := range rec.conns {
|
|
||||||
conn.mu.Lock()
|
|
||||||
sent := conn.sent
|
|
||||||
conn.mu.Unlock()
|
|
||||||
if len(sent) < 2 {
|
|
||||||
t.Errorf("connection %d sent %d datagrams", i, len(sent))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// the noise, then a QUIC v1 Initial of 1250 bytes with no source
|
|
||||||
// connection ID, as ChromeParrot sends it
|
|
||||||
first, initial := sent[0], sent[1]
|
|
||||||
if string(first) != "noise" || len(initial) != 1250 || initial[0]&0xf0 != 0xc0 || !bytes.Equal(initial[1:5], []byte{0, 0, 0, 1}) || initial[6+int(initial[5])] != 0 {
|
|
||||||
t.Errorf("connection %d began with %q and %d bytes %x", i, first, len(initial), initial[:min(len(initial), 24)])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// over h3 a connection of XMUX is a QUIC connection, which closes once XMUX
|
|
||||||
// drops it, so that the server hears of it
|
|
||||||
func TestDriveXmuxOverH3(t *testing.T) {
|
|
||||||
drive := newFakeDrive(t)
|
|
||||||
server := newH3Server(t, drive.server.Config.Handler)
|
|
||||||
driveTokenURL = "https://www.googleapis.com/token"
|
|
||||||
driveFilesURL = "https://www.googleapis.com/files"
|
|
||||||
driveUploadURL = "https://www.googleapis.com/upload"
|
|
||||||
settings := server.settings(&tls.Config{})
|
|
||||||
settings.ProtocolSettings = driveSettings().ProtocolSettings
|
|
||||||
settings.ProtocolSettings.(*Config).Xmux = &splithttp.XmuxConfig{HMaxRequestTimes: &splithttp.RangeConfig{From: 3, To: 3}}
|
|
||||||
storage, err := newDriveStorage(settings, settings.ProtocolSettings.(*Config))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("newDriveStorage: %v", err)
|
|
||||||
}
|
|
||||||
session := sessionStorage(storage)
|
|
||||||
defer session.Close()
|
|
||||||
|
|
||||||
for _, name := range []string{"a", "b", "c", "d"} {
|
|
||||||
if err := session.Put(context.Background(), "streams/abc/c2s/"+name, []byte("x")); err != nil {
|
|
||||||
t.Fatalf("Put: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// one for the token, the first of XMUX for 3 requests and one more
|
|
||||||
if n := server.conns.Load(); n != 3 {
|
|
||||||
t.Errorf("the token and 4 requests with 3 to a connection took %d connections", n)
|
|
||||||
}
|
|
||||||
waitFor(t, "the first connection of XMUX to close", func() bool { return server.closed.Load() == 1 })
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
package xdrive
|
package xdrive
|
||||||
|
|
||||||
import (
|
import (
|
||||||
splithttp "github.com/xtls/xray-core/transport/internet/splithttp"
|
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
@@ -36,7 +35,6 @@ type Config struct {
|
|||||||
EagerWindowMs uint32 `protobuf:"varint,10,opt,name=eager_window_ms,json=eagerWindowMs,proto3" json:"eager_window_ms,omitempty"`
|
EagerWindowMs uint32 `protobuf:"varint,10,opt,name=eager_window_ms,json=eagerWindowMs,proto3" json:"eager_window_ms,omitempty"`
|
||||||
HoleTimeoutMs uint32 `protobuf:"varint,11,opt,name=hole_timeout_ms,json=holeTimeoutMs,proto3" json:"hole_timeout_ms,omitempty"`
|
HoleTimeoutMs uint32 `protobuf:"varint,11,opt,name=hole_timeout_ms,json=holeTimeoutMs,proto3" json:"hole_timeout_ms,omitempty"`
|
||||||
Template string `protobuf:"bytes,12,opt,name=template,proto3" json:"template,omitempty"`
|
Template string `protobuf:"bytes,12,opt,name=template,proto3" json:"template,omitempty"`
|
||||||
Xmux *splithttp.XmuxConfig `protobuf:"bytes,13,opt,name=xmux,proto3" json:"xmux,omitempty"`
|
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
@@ -155,18 +153,11 @@ func (x *Config) GetTemplate() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Config) GetXmux() *splithttp.XmuxConfig {
|
|
||||||
if x != nil {
|
|
||||||
return x.Xmux
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var File_transport_internet_xdrive_config_proto protoreflect.FileDescriptor
|
var File_transport_internet_xdrive_config_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
const file_transport_internet_xdrive_config_proto_rawDesc = "" +
|
const file_transport_internet_xdrive_config_proto_rawDesc = "" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"&transport/internet/xdrive/config.proto\x12\x1exray.transport.internet.xdrive\x1a)transport/internet/splithttp/config.proto\"\x8e\x04\n" +
|
"&transport/internet/xdrive/config.proto\x12\x1exray.transport.internet.xdrive\"\xcb\x03\n" +
|
||||||
"\x06Config\x12#\n" +
|
"\x06Config\x12#\n" +
|
||||||
"\rremote_folder\x18\x01 \x01(\tR\fremoteFolder\x12\x18\n" +
|
"\rremote_folder\x18\x01 \x01(\tR\fremoteFolder\x12\x18\n" +
|
||||||
"\aservice\x18\x02 \x01(\tR\aservice\x12\x18\n" +
|
"\aservice\x18\x02 \x01(\tR\aservice\x12\x18\n" +
|
||||||
@@ -180,8 +171,7 @@ const file_transport_internet_xdrive_config_proto_rawDesc = "" +
|
|||||||
"\x0feager_window_ms\x18\n" +
|
"\x0feager_window_ms\x18\n" +
|
||||||
" \x01(\rR\reagerWindowMs\x12&\n" +
|
" \x01(\rR\reagerWindowMs\x12&\n" +
|
||||||
"\x0fhole_timeout_ms\x18\v \x01(\rR\rholeTimeoutMs\x12\x1a\n" +
|
"\x0fhole_timeout_ms\x18\v \x01(\rR\rholeTimeoutMs\x12\x1a\n" +
|
||||||
"\btemplate\x18\f \x01(\tR\btemplate\x12A\n" +
|
"\btemplate\x18\f \x01(\tR\btemplateB5Z3github.com/xtls/xray-core/transport/internet/xdriveb\x06proto3"
|
||||||
"\x04xmux\x18\r \x01(\v2-.xray.transport.internet.splithttp.XmuxConfigR\x04xmuxB5Z3github.com/xtls/xray-core/transport/internet/xdriveb\x06proto3"
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
file_transport_internet_xdrive_config_proto_rawDescOnce sync.Once
|
file_transport_internet_xdrive_config_proto_rawDescOnce sync.Once
|
||||||
@@ -198,15 +188,13 @@ func file_transport_internet_xdrive_config_proto_rawDescGZIP() []byte {
|
|||||||
var file_transport_internet_xdrive_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
var file_transport_internet_xdrive_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||||
var file_transport_internet_xdrive_config_proto_goTypes = []any{
|
var file_transport_internet_xdrive_config_proto_goTypes = []any{
|
||||||
(*Config)(nil), // 0: xray.transport.internet.xdrive.Config
|
(*Config)(nil), // 0: xray.transport.internet.xdrive.Config
|
||||||
(*splithttp.XmuxConfig)(nil), // 1: xray.transport.internet.splithttp.XmuxConfig
|
|
||||||
}
|
}
|
||||||
var file_transport_internet_xdrive_config_proto_depIdxs = []int32{
|
var file_transport_internet_xdrive_config_proto_depIdxs = []int32{
|
||||||
1, // 0: xray.transport.internet.xdrive.Config.xmux:type_name -> xray.transport.internet.splithttp.XmuxConfig
|
0, // [0:0] is the sub-list for method output_type
|
||||||
1, // [1:1] is the sub-list for method output_type
|
0, // [0:0] is the sub-list for method input_type
|
||||||
1, // [1:1] is the sub-list for method input_type
|
0, // [0:0] is the sub-list for extension type_name
|
||||||
1, // [1:1] is the sub-list for extension type_name
|
0, // [0:0] is the sub-list for extension extendee
|
||||||
1, // [1:1] is the sub-list for extension extendee
|
0, // [0:0] is the sub-list for field type_name
|
||||||
0, // [0:1] is the sub-list for field type_name
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { file_transport_internet_xdrive_config_proto_init() }
|
func init() { file_transport_internet_xdrive_config_proto_init() }
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ syntax = "proto3";
|
|||||||
package xray.transport.internet.xdrive;
|
package xray.transport.internet.xdrive;
|
||||||
option go_package = "github.com/xtls/xray-core/transport/internet/xdrive";
|
option go_package = "github.com/xtls/xray-core/transport/internet/xdrive";
|
||||||
|
|
||||||
import "transport/internet/splithttp/config.proto";
|
|
||||||
|
|
||||||
message Config {
|
message Config {
|
||||||
string remote_folder = 1;
|
string remote_folder = 1;
|
||||||
string service = 2;
|
string service = 2;
|
||||||
@@ -18,5 +16,4 @@ message Config {
|
|||||||
uint32 eager_window_ms = 10;
|
uint32 eager_window_ms = 10;
|
||||||
uint32 hole_timeout_ms = 11;
|
uint32 hole_timeout_ms = 11;
|
||||||
string template = 12;
|
string template = 12;
|
||||||
xray.transport.internet.splithttp.XmuxConfig xmux = 13;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ const (
|
|||||||
driveMaxAttempts = 8
|
driveMaxAttempts = 8
|
||||||
driveMaxInflight = 32
|
driveMaxInflight = 32
|
||||||
driveInlineLimit = 12000
|
driveInlineLimit = 12000
|
||||||
// driveTimeout ends a request that makes no progress for that long
|
|
||||||
driveTimeout = 60 * time.Second
|
driveTimeout = 60 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -39,21 +38,11 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type driveStorage struct {
|
type driveStorage struct {
|
||||||
*driveShared
|
|
||||||
// a session has a client and inflight of its own, see session
|
|
||||||
client *http.Client
|
|
||||||
inflight chan struct{}
|
|
||||||
release func()
|
|
||||||
}
|
|
||||||
|
|
||||||
// driveShared is what the sessions of a storage have in common
|
|
||||||
type driveShared struct {
|
|
||||||
conns *connPool
|
|
||||||
folder string
|
folder string
|
||||||
clientID string
|
clientID string
|
||||||
clientSecret string
|
clientSecret string
|
||||||
refreshToken string
|
refreshToken string
|
||||||
tokenClient *http.Client
|
client *http.Client
|
||||||
tokenURL string
|
tokenURL string
|
||||||
filesURL string
|
filesURL string
|
||||||
uploadURL string
|
uploadURL string
|
||||||
@@ -63,6 +52,8 @@ type driveShared struct {
|
|||||||
token string
|
token string
|
||||||
tokenExpiry time.Time
|
tokenExpiry time.Time
|
||||||
|
|
||||||
|
inflight chan struct{}
|
||||||
|
|
||||||
idMu sync.Mutex
|
idMu sync.Mutex
|
||||||
ids map[string]string
|
ids map[string]string
|
||||||
}
|
}
|
||||||
@@ -80,41 +71,21 @@ func newDriveStorage(streamSettings *internet.MemoryStreamConfig, config *Config
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
h1Hosts := new(sync.Map)
|
|
||||||
client := newServiceClient(context.Background(), h1Hosts, streamSettings, driveTimeout, driveMaxInflight)
|
|
||||||
return &driveStorage{
|
return &driveStorage{
|
||||||
driveShared: &driveShared{
|
|
||||||
conns: newConnPool(config.Xmux, func(connCtx context.Context) *http.Client {
|
|
||||||
return newServiceClient(connCtx, h1Hosts, streamSettings, driveTimeout, driveMaxInflight)
|
|
||||||
}),
|
|
||||||
folder: config.RemoteFolder,
|
folder: config.RemoteFolder,
|
||||||
clientID: config.Secrets[0],
|
clientID: config.Secrets[0],
|
||||||
clientSecret: config.Secrets[1],
|
clientSecret: config.Secrets[1],
|
||||||
refreshToken: config.Secrets[2],
|
refreshToken: config.Secrets[2],
|
||||||
tokenClient: client,
|
client: newServiceClient(streamSettings, driveTimeout, driveMaxInflight),
|
||||||
tokenURL: driveTokenURL,
|
tokenURL: driveTokenURL,
|
||||||
filesURL: driveFilesURL,
|
filesURL: driveFilesURL,
|
||||||
uploadURL: driveUploadURL,
|
uploadURL: driveUploadURL,
|
||||||
backoff: driveInitialBackoff,
|
backoff: driveInitialBackoff,
|
||||||
|
inflight: make(chan struct{}, driveMaxInflight),
|
||||||
ids: make(map[string]string),
|
ids: make(map[string]string),
|
||||||
},
|
|
||||||
client: client,
|
|
||||||
inflight: make(chan struct{}, driveMaxInflight),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// session returns a storage for one session, over the connections of XMUX
|
|
||||||
// until Close, with the token and ids of s
|
|
||||||
func (s *driveStorage) session() Storage {
|
|
||||||
transport := &sessionTransport{pool: s.conns}
|
|
||||||
return &driveStorage{
|
|
||||||
driveShared: s.driveShared,
|
|
||||||
client: &http.Client{Transport: transport},
|
|
||||||
inflight: make(chan struct{}, driveMaxInflight),
|
|
||||||
release: transport.Close,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func flatten(name string) string {
|
func flatten(name string) string {
|
||||||
return strings.ReplaceAll(name, "/", flatSeparator)
|
return strings.ReplaceAll(name, "/", flatSeparator)
|
||||||
}
|
}
|
||||||
@@ -143,7 +114,7 @@ func (s *driveStorage) accessToken(ctx context.Context) (string, error) {
|
|||||||
}
|
}
|
||||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
|
||||||
resp, err := s.tokenClient.Do(req)
|
resp, err := s.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.New("failed to refresh the access token").Base(err)
|
return "", errors.New("failed to refresh the access token").Base(err)
|
||||||
}
|
}
|
||||||
@@ -182,7 +153,7 @@ func jitter(backoff time.Duration) time.Duration {
|
|||||||
if half <= 0 {
|
if half <= 0 {
|
||||||
return backoff
|
return backoff
|
||||||
}
|
}
|
||||||
return half + time.Duration(dice.RollInt63n(int64(half)))
|
return half + time.Duration(dice.Roll(int(half)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func rateLimited(payload []byte) bool {
|
func rateLimited(payload []byte) bool {
|
||||||
@@ -600,8 +571,5 @@ func (s *driveStorage) List(ctx context.Context, prefix string) ([]Entry, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *driveStorage) Close() error {
|
func (s *driveStorage) Close() error {
|
||||||
if s.release != nil {
|
|
||||||
s.release()
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ func liveDriveConfig(t *testing.T) *Config {
|
|||||||
|
|
||||||
config := &Config{
|
config := &Config{
|
||||||
RemoteFolder: secrets.Folder,
|
RemoteFolder: secrets.Folder,
|
||||||
Service: "google-drive",
|
Service: "Google Drive",
|
||||||
Secrets: []string{secrets.ClientID, secrets.ClientSecret, secrets.RefreshToken},
|
Secrets: []string{secrets.ClientID, secrets.ClientSecret, secrets.RefreshToken},
|
||||||
SegmentBytes: 256 * 1024,
|
SegmentBytes: 256 * 1024,
|
||||||
FlushIntervalMs: 100,
|
FlushIntervalMs: 100,
|
||||||
|
|||||||
@@ -317,7 +317,7 @@ func driveSettings() *internet.MemoryStreamConfig {
|
|||||||
ProtocolName: protocolName,
|
ProtocolName: protocolName,
|
||||||
ProtocolSettings: &Config{
|
ProtocolSettings: &Config{
|
||||||
RemoteFolder: "folder-id",
|
RemoteFolder: "folder-id",
|
||||||
Service: "google-drive",
|
Service: "Google Drive",
|
||||||
Secrets: []string{"client", "secret", "refresh"},
|
Secrets: []string{"client", "secret", "refresh"},
|
||||||
FlushIntervalMs: 5,
|
FlushIntervalMs: 5,
|
||||||
PollIntervalMs: 5,
|
PollIntervalMs: 5,
|
||||||
@@ -562,22 +562,6 @@ func TestDriveSharedClient(t *testing.T) {
|
|||||||
if first != second {
|
if first != second {
|
||||||
t.Fatal("same settings did not share one storage")
|
t.Fatal("same settings did not share one storage")
|
||||||
}
|
}
|
||||||
// a session has an inflight of its own, as large
|
|
||||||
session := sessionStorage(first).(*driveStorage)
|
|
||||||
defer session.Close()
|
|
||||||
if session.inflight == first.(*driveStorage).inflight || cap(session.inflight) != driveMaxInflight {
|
|
||||||
t.Fatalf("a session shares inflight or has cap %d", cap(session.inflight))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDriveOldServiceName(t *testing.T) {
|
|
||||||
newFakeDrive(t)
|
|
||||||
|
|
||||||
settings := driveSettings()
|
|
||||||
settings.ProtocolSettings.(*Config).Service = "Google Drive"
|
|
||||||
if _, err := newStorage(settings); err != nil {
|
|
||||||
t.Fatalf("newStorage: %v", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDriveInlineListing(t *testing.T) {
|
func TestDriveInlineListing(t *testing.T) {
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ func newStorage(streamSettings *internet.MemoryStreamConfig) (Storage, error) {
|
|||||||
switch config.Service {
|
switch config.Service {
|
||||||
case "local":
|
case "local":
|
||||||
return newLocalStorage(config.RemoteFolder)
|
return newLocalStorage(config.RemoteFolder)
|
||||||
case "google-drive", "Google Drive":
|
case "Google Drive":
|
||||||
return sharedStorage(streamSettings, config, func() (Storage, error) {
|
return sharedStorage(streamSettings, config, func() (Storage, error) {
|
||||||
return newDriveStorage(streamSettings, config)
|
return newDriveStorage(streamSettings, config)
|
||||||
})
|
})
|
||||||
@@ -48,19 +48,6 @@ func newStorage(streamSettings *internet.MemoryStreamConfig) (Storage, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// sessionStorage returns the storage of one session, which takes connections
|
|
||||||
// of XMUX until Close, with the token and cache of storage. Local storage has
|
|
||||||
// no connections
|
|
||||||
func sessionStorage(storage Storage) Storage {
|
|
||||||
switch s := storage.(type) {
|
|
||||||
case *driveStorage:
|
|
||||||
return s.session()
|
|
||||||
case *templateStorage:
|
|
||||||
return s.session()
|
|
||||||
}
|
|
||||||
return storage
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
sharedMu sync.Mutex
|
sharedMu sync.Mutex
|
||||||
shared = make(map[string]Storage)
|
shared = make(map[string]Storage)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user