Compare commits

..
2 Commits
Author SHA1 Message Date
Fangliding df3ad31033 beauty 2026-09-09 00:25:55 +08:00
Fangliding 7a5721c50d Write encryption back to json struct to for validateOutboundTransportSecurity 2026-09-08 23:47:02 +08:00
121 changed files with 1891 additions and 12485 deletions
+3 -1
View File
@@ -67,7 +67,9 @@ jobs:
check-latest: true
cache: false
- name: Check Format
run: go run ./infra/vformat/main.go -mode check -pwd ./
run: |
go install -v mvdan.cc/gofumpt@latest
go run ./infra/vformat/main.go -mode check -pwd ./
test:
needs: check-assets
+1 -11
View File
@@ -5,8 +5,7 @@ import (
)
type windowsReader struct {
bufs []syscall.WSABuf
ready bool
bufs []syscall.WSABuf
}
func (r *windowsReader) Init(bs []*Buffer) {
@@ -16,7 +15,6 @@ func (r *windowsReader) Init(bs []*Buffer) {
for _, b := range bs {
r.bufs = append(r.bufs, syscall.WSABuf{Len: uint32(Size), Buf: &b.v[0]})
}
r.ready = false
}
func (r *windowsReader) Clear() {
@@ -27,14 +25,6 @@ func (r *windowsReader) Clear() {
}
func (r *windowsReader) Read(fd uintptr) int32 {
// On the first invocation, we return -1 to indicate "not ready"
// to make rawConn.Read wait for readability using the runtime's own mechanism
// because syscall.WSARecv() is a blocking call when used with nil OVERLAPPED
if !r.ready {
r.ready = true
return -1
}
var nBytes uint32
var flags uint32
err := syscall.WSARecv(syscall.Handle(fd), &r.bufs[0], uint32(len(r.bufs)), &nBytes, &flags, nil, nil)
+3 -3
View File
@@ -10,12 +10,12 @@ import (
// [,)
func RandBetween(from int64, to int64) int64 {
if from == to {
return from
}
if from > to {
from, to = to, from
}
if d := to - from; d == 0 || d == 1 {
return from
}
bigInt, _ := rand.Int(rand.Reader, big.NewInt(to-from))
return from + bigInt.Int64()
}
+2 -2
View File
@@ -19,8 +19,8 @@ import (
var (
Version_x byte = 26
Version_y byte = 9
Version_z byte = 9
Version_y byte = 7
Version_z byte = 28
)
var (
+10 -11
View File
@@ -1,6 +1,6 @@
module github.com/xtls/xray-core
go 1.27
go 1.26
require (
github.com/apernet/quic-go v0.61.1-0.20260806010916-184d081eef3e
@@ -24,20 +24,19 @@ require (
github.com/vishvananda/netlink v1.3.1
github.com/xtls/reality v0.0.0-20260908062103-8cdf7bf9c7f0
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
golang.org/x/crypto v0.57.0
golang.org/x/crypto v0.55.0
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
golang.org/x/net v0.59.0
golang.org/x/sync v0.23.0
golang.org/x/sys v0.48.0
golang.org/x/net v0.58.0
golang.org/x/sync v0.22.0
golang.org/x/sys v0.47.0
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb
golang.zx2c4.com/wireguard/windows v1.1.1
google.golang.org/grpc v1.84.0
golang.zx2c4.com/wireguard/windows v1.0.1
google.golang.org/grpc v1.83.2
google.golang.org/protobuf v1.36.12
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0
h12.io/socks v1.0.3
lukechampine.com/blake3 v1.4.1
mvdan.cc/gofumpt v0.12.0
)
require (
@@ -49,6 +48,7 @@ require (
github.com/juju/ratelimit v1.0.2 // indirect
github.com/klauspost/compress v1.17.4 // indirect
github.com/koron/go-ssdp v0.0.4 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/libp2p/go-netroute v0.2.1 // indirect
github.com/pion/dtls/v3 v3.1.5 // indirect
github.com/pion/logging v0.2.4 // indirect
@@ -57,9 +57,8 @@ require (
github.com/vishvananda/netns v0.0.5 // indirect
github.com/wlynxg/anet v0.0.5 // indirect
go.yaml.in/yaml/v3 v3.0.5 // indirect
golang.org/x/text v0.42.0 // indirect
golang.org/x/text v0.41.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-20260526163538-3dc84a4a5aaa // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
+37 -24
View File
@@ -2,12 +2,17 @@ github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sx
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/go.mod h1:x7qxEvX6MCVtDuBKHj3E+88+BtrbEMuAL5qGUKItjW8=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudflare/circl v1.6.5 h1:O64F26HEqNhznd/hrC5KZXVKYuKM2rx4deZDTc4ihQA=
github.com/cloudflare/circl v1.6.5/go.mod h1:h5LNyxAc5nTue9DS5jT+48en2PSDYt3zdGnz5OstK6c=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/go-quicktest/qt v1.102.0 h1:HSQxCeh5YZH3EL3W39ixjtyaEhcWSXQHtHnMBzSs474=
github.com/go-quicktest/qt v1.102.0/go.mod h1:p4lGIVX+8Wa6ZPNDvqcxq36XpUDLh42FLetFU7odllI=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=
github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
@@ -68,8 +73,8 @@ github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af h1:er
github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.16.0 h1:O9DK+vNMDVGLr2BeZqmpLeMjiMNkuXfcqntWbZV6S5g=
github.com/rogpeppe/go-internal v1.16.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/sagernet/sing v0.5.1 h1:mhL/MZVq0TjuvHcpYcFtmSD1BFOxZ/+8ofbNZcg1k1Y=
github.com/sagernet/sing v0.5.1/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
@@ -85,6 +90,18 @@ github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguH
github.com/xtls/reality v0.0.0-20260908062103-8cdf7bf9c7f0 h1:rb+fKQFhz+5I2PPuQsNYxI5mUU840XWYtRF0ZBjvkws=
github.com/xtls/reality v0.0.0-20260908062103-8cdf7bf9c7f0/go.mod h1:DsJblcWDGt76+FVqBVwbwRhxyyNJsGV48gJLch0OOWI=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58=
go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0=
go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI=
go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA=
go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
@@ -93,8 +110,8 @@ 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=
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.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M=
golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA=
golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M=
golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis=
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/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
@@ -103,12 +120,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-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.59.0 h1:5zfYln+w5XCxwrnMMJPufRgNoXEaGxl0wo5GqPXyues=
golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg=
golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU=
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.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk=
golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -116,22 +133,20 @@ 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.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo=
golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
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.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.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI=
golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E=
golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
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/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-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI=
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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -139,14 +154,14 @@ golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeu
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A=
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=
golang.zx2c4.com/wireguard/windows v1.1.1 h1:8/H97U1v1PNDNcBsMZgU3KFuND9MQdTsU2NOwmCXArE=
golang.zx2c4.com/wireguard/windows v1.1.1/go.mod h1:+fbT3FFdX4zzYDLwJh5+HPEcNN/3HyNdzhNSVsQM+zs=
golang.zx2c4.com/wireguard/windows v1.0.1 h1:eOxiDVbywPC+ZQqvdCK7x+ZwWXKbYv50TtH8ysFIbw8=
golang.zx2c4.com/wireguard/windows v1.0.1/go.mod h1:+fbT3FFdX4zzYDLwJh5+HPEcNN/3HyNdzhNSVsQM+zs=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800 h1:qEHAMpSaUhtD0p3NbEEI83HwNGFxEwaSJ1G9PLnCBZE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.84.0 h1:soMyaPJ8pAak5PIQ0DGBUir0XRo2fRoMqhNWMLlLxO0=
google.golang.org/grpc v1.84.0/go.mod h1:ljCht0DrxQrXBDRTZp52Qxh3Ffk8CdYm2sj4O2QN2C0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU=
google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8=
google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -161,5 +176,3 @@ h12.io/socks v1.0.3 h1:Ka3qaQewws4j4/eDQnOdpr4wXsC//dXtWvftlIcCQUo=
h12.io/socks v1.0.3/go.mod h1:AIhxy1jOId/XCz9BO+EIgNL2rQiPTBNnOfnVnQ+3Eck=
lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=
lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo=
mvdan.cc/gofumpt v0.12.0 h1:1Lbudkz2kpM9Cjz2pL4M19u7q+GaEhCTNf7N9mfpcho=
mvdan.cc/gofumpt v0.12.0/go.mod h1:SmBHHrljiZu/uoypeKup3rFzP6eoC9UwCp2iH5E3jZA=
-37
View File
@@ -1,37 +0,0 @@
package conf
import (
"net/netip"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/proxy/masque"
"google.golang.org/protobuf/proto"
)
type MasqueClientConfig struct {
Address *Address `json:"address"`
Port uint16 `json:"port"`
RemoteDNS []string `json:"remoteDNS"`
}
func (c *MasqueClientConfig) Build() (proto.Message, error) {
if c.Address == nil {
return nil, errors.New(`MASQUE: "address" is not set`)
}
if c.Port == 0 {
return nil, errors.New(`MASQUE: "port" is not set`)
}
for _, s := range c.RemoteDNS {
if _, err := netip.ParseAddr(s); err != nil {
return nil, errors.New(`MASQUE: invalid "remoteDNS" `, s).Base(err)
}
}
return &masque.ClientConfig{
Server: &protocol.ServerEndpoint{
Address: c.Address.Build(),
Port: uint32(c.Port),
},
RemoteDns: c.RemoteDNS,
}, nil
}
-85
View File
@@ -1,85 +0,0 @@
package conf_test
import (
"encoding/json"
"testing"
. "github.com/xtls/xray-core/infra/conf"
"github.com/xtls/xray-core/transport/internet/masque"
)
func TestMasqueConfig(t *testing.T) {
creator := func() Buildable {
return new(MasqueConfig)
}
runMultiTestCase(t, []TestCase{
{
Input: `{}`,
Parser: loadJSON(creator),
Output: &masque.Config{Path: "/.well-known/masque/ip/*/*/"},
},
{
Input: `{
"host": "example.com:8443",
"path": "/.well-known/masque/ip/{target}/{ipproto}/",
"headers": {"Authorization": "Basic dTpw"}
}`,
Parser: loadJSON(creator),
Output: &masque.Config{
Host: "example.com:8443",
Path: "/.well-known/masque/ip/*/*/",
Headers: map[string]string{"Authorization": "Basic dTpw"},
},
},
{
Input: `{"path": "/masque/ip{?target,ipproto}"}`,
Parser: loadJSON(creator),
Output: &masque.Config{Path: "/masque/ip?target=*&ipproto=*"},
},
})
for _, input := range []string{
`{"path": "/masque/{target}/{ipproto}/{dns}"}`,
`{"path": "masque"}`,
`{"host": "example.com/path"}`,
`{"headers": {"host": "example.com"}}`,
`{"headers": {"Capsule-Protocol": "?0"}}`,
`{"headers": {"X Token": "a"}}`,
`{"headers": {"X-Token": "a\r\nb"}}`,
} {
if _, err := loadJSON(creator)(input); err == nil {
t.Errorf("expected an error for %s", input)
}
}
}
func TestMasqueOutboundConfig(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
}
if err := build(`{
"protocol": "masque",
"settings": {"address": "example.com", "port": 443},
"streamSettings": {"network": "masque", "security": "tls"},
"mux": {"enabled": false, "concurrency": -1}
}`); err != nil {
t.Error(err)
}
for _, input := range []string{
`{"protocol": "masque", "settings": {"address": "example.com"}, "streamSettings": {"network": "masque", "security": "tls"}}`,
`{"protocol": "masque", "settings": {"address": "example.com", "port": 443}, "streamSettings": {"network": "masque", "security": "tls"}, "mux": {"enabled": true}}`,
`{"protocol": "masque", "settings": {"address": "example.com", "port": 443}, "streamSettings": {"network": "masque", "security": "tls"}, "mux": {"enabled": true, "concurrency": -1}}`,
`{"protocol": "freedom", "streamSettings": {"network": "masque", "security": "tls"}}`,
} {
if err := build(input); err == nil {
t.Errorf("expected an error for %s", input)
}
}
}
+1 -48
View File
@@ -23,7 +23,6 @@ import (
"github.com/xtls/xray-core/transport/internet/finalmask/realm"
"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/udphop"
"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/xmc"
@@ -84,7 +83,6 @@ var (
"xdns": func() interface{} { return new(Xdns) },
"xicmp": func() interface{} { return new(Xicmp) },
"realm": func() interface{} { return new(Realm) },
"udphop": func() interface{} { return new(UDPHop) },
}, "type", "settings")
)
@@ -907,52 +905,6 @@ func (c *Realm) Build() (proto.Message, error) {
}, nil
}
type UDPHop struct {
Mode string `json:"mode"`
Interval Int32Range `json:"interval"`
RemoteIPs []string `json:"remoteIPs"`
RemotePorts PortList `json:"remotePorts"`
}
func (c *UDPHop) Build() (proto.Message, error) {
var local, remote, remoteOnce bool
for _, mode := range strings.Split(c.Mode, ",") {
switch strings.ToLower(mode) {
case "intervallocal":
local = true
case "intervalremote":
remote = true
case "perconnremote":
remoteOnce = true
default:
return nil, errors.New("invalid mode ", mode)
}
}
var remoteIPs []string
for _, ip := range c.RemoteIPs {
prefix, err := netip.ParsePrefix(ip)
if err == nil {
remoteIPs = append(remoteIPs, prefix.String())
continue
}
addr, err := netip.ParseAddr(ip)
if err == nil {
remoteIPs = append(remoteIPs, netip.PrefixFrom(addr, addr.BitLen()).String())
continue
}
return nil, errors.New("invalid ip ", ip)
}
return &udphop.Config{
Local: local,
Remote: remote,
RemoteOnce: remoteOnce,
IntervalMin: int64(c.Interval.From),
IntervalMax: int64(c.Interval.To),
RemoteIPs: remoteIPs,
RemotePorts: c.RemotePorts.Build().Ports(),
}, nil
}
type Mask struct {
Type string `json:"type"`
Settings *json.RawMessage `json:"settings"`
@@ -986,6 +938,7 @@ type QuicParamsConfig struct {
BrutalUp Bandwidth `json:"brutalUp"`
BrutalDown Bandwidth `json:"brutalDown"`
BrutalDisableLossCompensation bool `json:"brutalDisableLossCompensation"`
UdpHop UdpHop `json:"udpHop"`
InitStreamReceiveWindow uint64 `json:"initStreamReceiveWindow"`
MaxStreamReceiveWindow uint64 `json:"maxStreamReceiveWindow"`
InitConnectionReceiveWindow uint64 `json:"initConnectionReceiveWindow"`
+20 -37
View File
@@ -36,10 +36,6 @@ func (p TransportProtocol) Build() (string, error) {
return "", errors.PrintRemovedFeatureError("QUIC transport (without web service, etc.)", "XHTTP stream-one H3")
case "hysteria":
return "hysteria", nil
case "masque":
return "masque", nil
case "xdrive":
return "xdrive", nil
default:
return "", errors.New("Config: unknown transport protocol: ", p)
}
@@ -63,8 +59,6 @@ type StreamConfig struct {
WSSettings *WebSocketConfig `json:"wsSettings"`
HTTPUPGRADESettings *HttpUpgradeConfig `json:"httpupgradeSettings"`
HysteriaSettings *HysteriaConfig `json:"hysteriaSettings"`
MASQUESettings *MasqueConfig `json:"masqueSettings"`
XDRIVESettings *XDriveConfig `json:"xdriveSettings"`
SocketSettings *SocketConfig `json:"sockopt"`
}
@@ -198,26 +192,6 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
Settings: serial.ToTypedMessage(hs),
})
}
if c.MASQUESettings != nil {
ms, err := c.MASQUESettings.Build()
if err != nil {
return nil, errors.New("Failed to build MASQUE config.").Base(err)
}
config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{
ProtocolName: "masque",
Settings: serial.ToTypedMessage(ms),
})
}
if c.XDRIVESettings != nil {
xs, err := c.XDRIVESettings.Build()
if err != nil {
return nil, errors.New("Failed to build XDRIVE config.").Base(err)
}
config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{
ProtocolName: "xdrive",
Settings: serial.ToTypedMessage(xs),
})
}
if c.SocketSettings != nil {
ss, err := c.SocketSettings.Build()
if err != nil {
@@ -279,6 +253,10 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
return nil, errors.New("unknown congestion control: ", c.FinalMask.QuicParams.Congestion, ", valid values: reno, bbr, brutal, force-brutal")
}
if (c.FinalMask.QuicParams.UdpHop.Interval.From != 0 && c.FinalMask.QuicParams.UdpHop.Interval.From < 5) || (c.FinalMask.QuicParams.UdpHop.Interval.To != 0 && c.FinalMask.QuicParams.UdpHop.Interval.To < 5) {
return nil, errors.New("Interval must be at least 5")
}
if c.FinalMask.QuicParams.InitStreamReceiveWindow > 0 && c.FinalMask.QuicParams.InitStreamReceiveWindow < 16384 {
return nil, errors.New("InitStreamReceiveWindow must be at least 16384")
}
@@ -312,17 +290,22 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
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,
UdpHop: &internet.UdpHop{
Ports: c.FinalMask.QuicParams.UdpHop.PortList.Build().Ports(),
IntervalMin: int64(c.FinalMask.QuicParams.UdpHop.Interval.From),
IntervalMax: int64(c.FinalMask.QuicParams.UdpHop.Interval.To),
},
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,
}
}
}
+26 -96
View File
@@ -1,6 +1,7 @@
package conf
import (
"context"
"encoding/json"
"math/big"
"net/url"
@@ -20,12 +21,9 @@ import (
"github.com/xtls/xray-core/transport/internet/httpupgrade"
"github.com/xtls/xray-core/transport/internet/hysteria"
"github.com/xtls/xray-core/transport/internet/kcp"
"github.com/xtls/xray-core/transport/internet/masque"
"github.com/xtls/xray-core/transport/internet/splithttp"
"github.com/xtls/xray-core/transport/internet/tcp"
"github.com/xtls/xray-core/transport/internet/websocket"
"github.com/xtls/xray-core/transport/internet/xdrive"
"golang.org/x/net/http/httpguts"
"google.golang.org/protobuf/proto"
)
@@ -536,6 +534,10 @@ type KCPConfig struct {
// Build implements Buildable.
func (c *KCPConfig) Build() (proto.Message, error) {
if c.HeaderConfig != nil || c.Seed != nil {
return nil, errors.PrintRemovedFeatureError("mkcp header & seed", "finalmask/udp header-* & mkcp-original & mkcp-aes128gcm")
}
config := common.Must2(internet.CreateTransportConfig(kcp.ProtocolName)).(*kcp.Config)
if c.Mtu != nil {
@@ -558,16 +560,16 @@ func (c *KCPConfig) Build() (proto.Message, error) {
}
if config.Mtu < 21 {
return nil, errors.New("MTU must be at least 21")
return nil, errors.New("Mtu must be at least 21").AtError()
}
if config.Tti < 10 || config.Tti > 1000 {
return nil, errors.New("TTI must be between 10 and 1000")
return nil, errors.New("invalid mKCP TTI: ", c.Tti).AtError()
}
if config.CwndMultiplier < 1 {
return nil, errors.New("CwndMultiplier must be at least 1")
return nil, errors.New("CwndMultiplier must be at least 1").AtError()
}
if config.GetSendingBufferSize() == 0 {
return nil, errors.New("MaxSendingWindow must be at least ", config.Mtu)
return nil, errors.New("MaxSendingWindow must be >= Mtu").AtError()
}
return config, nil
@@ -737,6 +739,11 @@ func (b Bandwidth) Bps() (uint64, error) {
return uint64(val*float64(mul)) / 8, nil
}
type UdpHop struct {
PortList PortList `json:"ports"`
Interval Int32Range `json:"interval"`
}
type Masquerade struct {
Type string `json:"type"`
@@ -753,8 +760,14 @@ type Masquerade struct {
}
type HysteriaConfig struct {
Version int32 `json:"version"`
Auth string `json:"auth"`
Version int32 `json:"version"`
Auth string `json:"auth"`
Congestion *string `json:"congestion"`
Up *Bandwidth `json:"up"`
Down *Bandwidth `json:"down"`
UdpHop *UdpHop `json:"udphop"`
UdpIdleTimeout int64 `json:"udpIdleTimeout"`
Masquerade Masquerade `json:"masquerade"`
}
@@ -764,6 +777,10 @@ func (c *HysteriaConfig) Build() (proto.Message, error) {
return nil, errors.New("version != 2")
}
if c.Congestion != nil || c.Up != nil || c.Down != nil || c.UdpHop != nil {
errors.LogWarning(context.Background(), "congestion & up & down & udphop move to finalmask/quicParams")
}
if c.UdpIdleTimeout != 0 && (c.UdpIdleTimeout < 2 || c.UdpIdleTimeout > 600) {
return nil, errors.New("UdpIdleTimeout must be between 2 and 600")
}
@@ -788,46 +805,6 @@ func (c *HysteriaConfig) Build() (proto.Message, error) {
return config, nil
}
type MasqueConfig struct {
Host string `json:"host"`
Path string `json:"path"`
Headers map[string]string `json:"headers"`
}
func (c *MasqueConfig) Build() (proto.Message, error) {
path := c.Path
if path == "" {
path = masque.DefaultPath
}
path = strings.NewReplacer(
"{target}", "*", "{ipproto}", "*",
"{?target,ipproto}", "?target=*&ipproto=*", "{?ipproto,target}", "?ipproto=*&target=*",
"{&target,ipproto}", "&target=*&ipproto=*", "{&ipproto,target}", "&ipproto=*&target=*",
).Replace(path)
if !strings.HasPrefix(path, "/") || strings.ContainsAny(path, "{}") {
return nil, errors.New(`invalid "path": `, path, `, only the variables {target} and {ipproto} are supported`)
}
if c.Host != "" {
if u, err := url.Parse("https://" + c.Host); err != nil || u.Host != c.Host {
return nil, errors.New(`invalid "host": `, c.Host)
}
}
for k, v := range c.Headers {
if !httpguts.ValidHeaderFieldName(k) || !httpguts.ValidHeaderFieldValue(v) {
return nil, errors.New(`invalid header in "headers": `, strconv.Quote(k))
}
switch strings.ToLower(k) {
case "host", "capsule-protocol":
return nil, errors.New(`"headers" can't contain "`, k, `"`)
}
}
return &masque.Config{
Host: c.Host,
Path: path,
Headers: c.Headers,
}, nil
}
func readFileOrString(f string, s []string) ([]byte, error) {
if len(f) > 0 {
return filesystem.ReadCert(f)
@@ -837,50 +814,3 @@ func readFileOrString(f string, s []string) ([]byte, error) {
}
return nil, errors.New("both file and bytes are empty.")
}
type XDriveConfig struct {
RemoteFolder string `json:"remoteFolder"`
Service string `json:"service"`
Secrets []string `json:"secrets"`
SegmentBytes uint32 `json:"segmentBytes"`
FlushIntervalMs uint32 `json:"flushIntervalMs"`
PollIntervalMs uint32 `json:"pollIntervalMs"`
MaxPollIntervalMs uint32 `json:"maxPollIntervalMs"`
SessionTTLSeconds uint32 `json:"sessionTtlSeconds"`
Concurrency uint32 `json:"concurrency"`
EagerWindowMs uint32 `json:"eagerWindowMs"`
HoleTimeoutMs uint32 `json:"holeTimeoutMs"`
Template json.RawMessage `json:"template"`
}
// Build implements Buildable.
func (c *XDriveConfig) Build() (proto.Message, error) {
switch c.Service {
case "local":
case "Google Drive":
if len(c.Secrets) != 3 {
return nil, errors.New("Google Drive needs 3 secrets in order of ClientID, ClientSecret, RefreshToken")
}
case "template":
if len(c.Template) == 0 {
return nil, errors.New(`service "template" needs a "template" object`)
}
default:
return nil, errors.New("unsupported service")
}
config := &xdrive.Config{
RemoteFolder: c.RemoteFolder,
Service: c.Service,
Secrets: c.Secrets,
SegmentBytes: c.SegmentBytes,
FlushIntervalMs: c.FlushIntervalMs,
PollIntervalMs: c.PollIntervalMs,
MaxPollIntervalMs: c.MaxPollIntervalMs,
SessionTtlSeconds: c.SessionTTLSeconds,
Concurrency: c.Concurrency,
EagerWindowMs: c.EagerWindowMs,
HoleTimeoutMs: c.HoleTimeoutMs,
Template: string(c.Template),
}
return config, nil
}
-73
View File
@@ -291,76 +291,3 @@ func TestHeaderCustomUDPBuildRejectsExprWithoutArgs(t *testing.T) {
t.Fatalf("expected transform arg rejection, got %v", err)
}
}
func TestXDriveStreamConfig(t *testing.T) {
config := new(StreamConfig)
if err := json.Unmarshal([]byte(`{
"method": "xdrive",
"xdriveSettings": {
"remoteFolder": "/tmp/xdrive",
"service": "local"
}
}`), config); err != nil {
t.Fatalf("Unmarshal: %v", err)
}
built, err := config.Build()
if err != nil {
t.Fatalf("Build: %v", err)
}
if built.ProtocolName != "xdrive" {
t.Fatalf("ProtocolName is %q, want %q", built.ProtocolName, "xdrive")
}
if len(built.TransportSettings) != 1 || built.TransportSettings[0].ProtocolName != "xdrive" {
t.Fatalf("TransportSettings is %v, want a single xdrive entry", built.TransportSettings)
}
}
func TestXDriveRejectsUnknownService(t *testing.T) {
config := new(XDriveConfig)
if err := json.Unmarshal([]byte(`{"remoteFolder": "/tmp/xdrive", "service": "Dropbox"}`), config); err != nil {
t.Fatalf("Unmarshal: %v", err)
}
if _, err := config.Build(); err == nil {
t.Fatal("Build accepted an unsupported service")
}
}
func TestXDriveTemplateStreamConfig(t *testing.T) {
config := new(StreamConfig)
if err := json.Unmarshal([]byte(`{
"method": "xdrive",
"xdriveSettings": {
"remoteFolder": "folder",
"service": "template",
"secrets": ["user", "pass"],
"template": {
"flatten": true,
"auth": {"type": "basic", "username": "{secret0}", "password": "{secret1}"},
"put": {"method": "PUT", "url": "https://dav.example/{folder}/{name}"},
"get": {"method": "GET", "url": "https://dav.example/{folder}/{name}"},
"delete": {"method": "DELETE", "url": "https://dav.example/{folder}/{name}"},
"list": {"method": "PROPFIND", "url": "https://dav.example/{folder}/", "namesRegex": "<d:href>/folder/([^<]+)</d:href>"}
}
}
}`), config); err != nil {
t.Fatalf("Unmarshal: %v", err)
}
built, err := config.Build()
if err != nil {
t.Fatalf("Build: %v", err)
}
if built.ProtocolName != "xdrive" {
t.Fatalf("ProtocolName is %q, want xdrive", built.ProtocolName)
}
}
func TestXDriveTemplateNeedsTemplate(t *testing.T) {
config := new(XDriveConfig)
if err := json.Unmarshal([]byte(`{"remoteFolder": "f", "service": "template"}`), config); err != nil {
t.Fatalf("Unmarshal: %v", err)
}
if _, err := config.Build(); err == nil {
t.Fatal("Build accepted a template service without a template")
}
}
+23 -7
View File
@@ -59,13 +59,14 @@ func (c *WireGuardPeerConfig) Build() (*wireguard.PeerConfig, error) {
type WireGuardConfig struct {
IsClient bool `json:""`
NoKernelTun bool `json:"noKernelTun"`
SecretKey string `json:"secretKey"`
Address []string `json:"address"`
Peers []*WireGuardPeerConfig `json:"peers"`
MTU int32 `json:"mtu"`
Reserved []byte `json:"reserved"`
DNS []string `json:"remoteDNS"`
NoKernelTun bool `json:"noKernelTun"`
SecretKey string `json:"secretKey"`
Address []string `json:"address"`
Peers []*WireGuardPeerConfig `json:"peers"`
MTU int32 `json:"mtu"`
Reserved []byte `json:"reserved"`
DomainStrategy string `json:"domainStrategy"`
DNS []string `json:"remoteDNS"`
}
func (c *WireGuardConfig) Build() (proto.Message, error) {
@@ -124,6 +125,21 @@ func (c *WireGuardConfig) Build() (proto.Message, error) {
}
config.Reserved = c.Reserved
switch strings.ToLower(c.DomainStrategy) {
case "forceip", "":
config.DomainStrategy = wireguard.DeviceConfig_FORCE_IP
case "forceipv4":
config.DomainStrategy = wireguard.DeviceConfig_FORCE_IP4
case "forceipv6":
config.DomainStrategy = wireguard.DeviceConfig_FORCE_IP6
case "forceipv4v6":
config.DomainStrategy = wireguard.DeviceConfig_FORCE_IP46
case "forceipv6v4":
config.DomainStrategy = wireguard.DeviceConfig_FORCE_IP64
default:
return nil, errors.New("unsupported domain strategy: ", c.DomainStrategy)
}
config.IsClient = c.IsClient
config.NoKernelTun = c.NoKernelTun
config.DNS = c.DNS
-10
View File
@@ -16,7 +16,6 @@ import (
"github.com/xtls/xray-core/common/serial"
core "github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/proxy/freedom"
"github.com/xtls/xray-core/proxy/masque"
"github.com/xtls/xray-core/transport/internet"
)
@@ -49,7 +48,6 @@ var (
"vmess": func() interface{} { return new(VMessOutboundConfig) },
"trojan": func() interface{} { return new(TrojanClientConfig) },
"hysteria": func() interface{} { return new(HysteriaClientConfig) },
"masque": func() interface{} { return new(MasqueClientConfig) },
"dns": func() interface{} { return new(DNSOutboundConfig) },
"wireguard": func() interface{} { return &WireGuardConfig{IsClient: true} },
}, "protocol", "settings")
@@ -340,14 +338,6 @@ func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) {
return nil, err
}
if _, ok := ts.(*masque.ClientConfig); ok {
if ms := senderSettings.MultiplexSettings; ms != nil && ms.Enabled {
return nil, errors.New(`masque outbound does not support "mux"`)
}
} else if senderSettings.StreamSettings != nil && senderSettings.StreamSettings.ProtocolName == "masque" {
return nil, errors.New("the masque transport can only be used by the masque outbound")
}
if fc, ok := ts.(*freedom.Config); ok {
if senderSettings.StreamSettings != nil &&
senderSettings.StreamSettings.SocketSettings != nil &&
+138 -272
View File
@@ -1,18 +1,15 @@
package main
import (
"bytes"
"errors"
"flag"
"fmt"
"go/build"
"os"
"os/exec"
"path/filepath"
"runtime"
"sort"
"strings"
"sync"
"sync/atomic"
"mvdan.cc/gofumpt/format"
)
var (
@@ -26,27 +23,101 @@ var (
isFormat bool
)
func getModuleInfo(pwd string) (modPath, langVersion string, err error) {
data, err := os.ReadFile(filepath.Join(pwd, "go.mod"))
if err != nil {
return "", "", err
}
for _, line := range strings.Split(string(data), "\n") {
fields := strings.Fields(line)
if len(fields) >= 2 {
switch fields[0] {
case "module":
modPath = fields[1]
case "go":
langVersion = "go" + strings.TrimPrefix(fields[1], "go")
}
// envFile returns the name of the Go environment configuration file.
// Copy from https://github.com/golang/go/blob/c4f2a9788a7be04daf931ac54382fbe2cb754938/src/cmd/go/internal/cfg/cfg.go#L150-L166
func envFile() (string, error) {
if file := os.Getenv("GOENV"); file != "" {
if file == "off" {
return "", errors.New("GOENV=off")
}
return file, nil
}
return modPath, langVersion, nil
dir, err := os.UserConfigDir()
if err != nil {
return "", err
}
if dir == "" {
return "", errors.New("missing user-config dir")
}
return filepath.Join(dir, "go", "env"), nil
}
func formatGoSource(src []byte, opts format.Options) ([]byte, error) {
return format.Source(src, opts)
// GetRuntimeEnv returns the value of runtime environment variable,
// that is set by running following command: `go env -w key=value`.
func GetRuntimeEnv(key string) (string, error) {
file, err := envFile()
if err != nil {
return "", err
}
if file == "" {
return "", errors.New("missing runtime env file")
}
var data []byte
var runtimeEnv string
data, readErr := os.ReadFile(file)
if readErr != nil {
return "", readErr
}
envStrings := strings.Split(string(data), "\n")
for _, envItem := range envStrings {
envItem = strings.TrimSuffix(envItem, "\r")
envKeyValue := strings.Split(envItem, "=")
if len(envKeyValue) == 2 && strings.TrimSpace(envKeyValue[0]) == key {
runtimeEnv = strings.TrimSpace(envKeyValue[1])
}
}
return runtimeEnv, nil
}
// GetGOBIN returns GOBIN environment variable as a string. It will NOT be empty.
func GetGOBIN() string {
// The one set by user explicitly by `export GOBIN=/path` or `env GOBIN=/path command`
GOBIN := os.Getenv("GOBIN")
if GOBIN == "" {
var err error
// The one set by user by running `go env -w GOBIN=/path`
GOBIN, err = GetRuntimeEnv("GOBIN")
if err != nil {
// The default one that Golang uses
return filepath.Join(build.Default.GOPATH, "bin")
}
if GOBIN == "" {
return filepath.Join(build.Default.GOPATH, "bin")
}
return GOBIN
}
return GOBIN
}
func Run(binary string, args []string) ([]byte, error) {
cmd := exec.Command(binary, args...)
cmd.Env = append(cmd.Env, os.Environ()...)
output, cmdErr := cmd.CombinedOutput()
if cmdErr != nil {
return nil, cmdErr
}
return output, nil
}
func RunMany(binary string, args, files []string) bool {
fmt.Println("Processing with", binary, args, "...")
formatRequired := false
maxTasks := make(chan struct{}, runtime.NumCPU())
for _, file := range files {
maxTasks <- struct{}{}
go func(file string) {
output, err := Run(binary, append(args, file))
if err != nil {
fmt.Println(err)
} else if len(output) > 0 {
fmt.Println(string(output))
formatRequired = true
}
<-maxTasks
}(file)
}
return formatRequired
}
func main() {
@@ -79,76 +150,26 @@ func main() {
}
pwd := *directory
modPath, langVersion, modErr := getModuleInfo(pwd)
if modErr != nil {
fmt.Println("Error reading go.mod:", modErr)
GOBIN := GetGOBIN()
binPath := os.Getenv("PATH")
pathSlice := []string{pwd, GOBIN, binPath}
binPath = strings.Join(pathSlice, string(os.PathListSeparator))
os.Setenv("PATH", binPath)
suffix := ""
if runtime.GOOS == "windows" {
suffix = ".exe"
}
gofmt := "gofumpt" + suffix
if gofmtPath, err := exec.LookPath(gofmt); err != nil {
fmt.Println("Can not find", gofmt, "in system path or current working directory.")
os.Exit(1)
}
opts := format.Options{
LangVersion: langVersion,
ModulePath: modPath,
}
if isFormat {
fmt.Println("Formatting Go source files...")
} else if isCheck {
fmt.Println("Checking files thar are not properly formatted...")
}
jobs := make(chan string, runtime.NumCPU())
var wg sync.WaitGroup
var formatRequired atomic.Bool
var hasErrors atomic.Bool
for i := 0; i < runtime.NumCPU(); i++ {
wg.Go(func() {
for path := range jobs {
src, err := os.ReadFile(path)
if err != nil {
fmt.Fprintf(os.Stderr, "Error reading %s: %v\n", path, err)
hasErrors.Store(true)
continue
}
formatted, err := formatGoSource(src, opts)
if err != nil {
fmt.Fprintf(os.Stderr, "Error formatting %s: %v\n", path, err)
hasErrors.Store(true)
continue
}
if !bytes.Equal(src, formatted) {
var diffText []byte
if isDryrun {
newName := filepath.ToSlash(path)
oldName := newName + ".orig"
diffText = diff(oldName, src, newName, formatted)
}
if isFormat {
info, statErr := os.Stat(path)
if statErr != nil {
fmt.Fprintf(os.Stderr, "Error stating %s: %v\n", path, statErr)
hasErrors.Store(true)
continue
}
if writeErr := os.WriteFile(path, formatted, info.Mode().Perm()); writeErr != nil {
fmt.Fprintf(os.Stderr, "Error writing %s: %v\n", path, writeErr)
hasErrors.Store(true)
continue
}
}
formatRequired.Store(true)
if isDryrun && len(diffText) > 0 {
fmt.Printf("%s\n%s", path, diffText)
} else {
fmt.Println(path)
}
}
}
})
} else {
gofmt = gofmtPath
}
rawFilesSlice := make([]string, 0, 1000)
walkErr := filepath.Walk(pwd, func(path string, info os.FileInfo, err error) error {
if err != nil {
fmt.Println(err)
@@ -165,206 +186,51 @@ func main() {
!strings.HasSuffix(filename, ".pb.go") &&
!strings.Contains(dir, filepath.Join("testing", "mocks")) &&
!strings.Contains(path, filepath.Join("main", "distro", "all", "all.go")) {
jobs <- path
rawFilesSlice = append(rawFilesSlice, path)
}
return nil
})
close(jobs)
wg.Wait()
if walkErr != nil {
fmt.Println(walkErr)
os.Exit(1)
}
if hasErrors.Load() {
os.Exit(1)
}
if isFormat {
if formatRequired.Load() {
fmt.Println("Do NOT forget to commit file changes.")
gofmtArgs := []string{
"-l", "-e", "-w",
}
fmt.Println("Formatting Go source files...")
RunMany(gofmt, gofmtArgs, rawFilesSlice)
fmt.Println("Do NOT forget to commit file changes.")
}
if isCheck {
if formatRequired.Load() {
gofmtListArgs := []string{
"-l", "-e",
}
fmt.Println("Checking files thar are not properly formatted...")
formatRequired := RunMany(gofmt, gofmtListArgs, rawFilesSlice)
if formatRequired {
fmt.Println("Format problem(s) found.")
fmt.Println("Please run 'go run ./infra/vformat/main.go' to format the Go source files.")
}
if isDryrun {
if formatRequired {
gofmtShowArgs := []string{
"-d", "-e",
}
RunMany(gofmt, gofmtShowArgs, rawFilesSlice)
}
}
if formatRequired {
fmt.Println("Please run 'go install -v mvdan.cc/gofumpt@latest', then run 'go run ./infra/vformat/main.go' to format the Go source files.")
os.Exit(1)
} else {
fmt.Println("All Go source file format check has been passed.")
}
}
}
// diff algorithm copied from mvdan.cc/gofumpt/internal/govendor/diff
type pair struct{ x, y int }
func diff(oldName string, old []byte, newName string, new []byte) []byte {
if bytes.Equal(old, new) {
return nil
}
x := diffLines(old)
y := diffLines(new)
var out bytes.Buffer
fmt.Fprintf(&out, "diff %s %s\n", oldName, newName)
fmt.Fprintf(&out, "--- %s\n", oldName)
fmt.Fprintf(&out, "+++ %s\n", newName)
var (
done pair
chunk pair
count pair
ctext []string
)
for _, m := range diffTgs(x, y) {
if m.x < done.x {
continue
}
start := m
for start.x > done.x && start.y > done.y && x[start.x-1] == y[start.y-1] {
start.x--
start.y--
}
end := m
for end.x < len(x) && end.y < len(y) && x[end.x] == y[end.y] {
end.x++
end.y++
}
for _, s := range x[done.x:start.x] {
ctext = append(ctext, "-"+s)
count.x++
}
for _, s := range y[done.y:start.y] {
ctext = append(ctext, "+"+s)
count.y++
}
const C = 3
if (end.x < len(x) || end.y < len(y)) &&
(end.x-start.x < C || (len(ctext) > 0 && end.x-start.x < 2*C)) {
for _, s := range x[start.x:end.x] {
ctext = append(ctext, " "+s)
count.x++
count.y++
}
done = end
continue
}
if len(ctext) > 0 {
n := end.x - start.x
if n > C {
n = C
}
for _, s := range x[start.x : start.x+n] {
ctext = append(ctext, " "+s)
count.x++
count.y++
}
done = pair{start.x + n, start.y + n}
if count.x > 0 {
chunk.x++
}
if count.y > 0 {
chunk.y++
}
fmt.Fprintf(&out, "@@ -%d,%d +%d,%d @@\n", chunk.x, count.x, chunk.y, count.y)
for _, s := range ctext {
out.WriteString(s)
}
count.x = 0
count.y = 0
ctext = ctext[:0]
}
if end.x >= len(x) && end.y >= len(y) {
break
}
chunk = pair{end.x - C, end.y - C}
for _, s := range x[chunk.x:end.x] {
ctext = append(ctext, " "+s)
count.x++
count.y++
}
done = end
}
return out.Bytes()
}
func diffLines(x []byte) []string {
l := strings.SplitAfter(string(x), "\n")
if l[len(l)-1] == "" {
l = l[:len(l)-1]
} else {
l[len(l)-1] += "\n\\ No newline at end of file\n"
}
return l
}
func diffTgs(x, y []string) []pair {
m := make(map[string]int)
for _, s := range x {
if c := m[s]; c > -2 {
m[s] = c - 1
}
}
for _, s := range y {
if c := m[s]; c > -8 {
m[s] = c - 4
}
}
var xi, yi, inv []int
for i, s := range y {
if m[s] == -5 {
m[s] = len(yi)
yi = append(yi, i)
}
}
for i, s := range x {
if j, ok := m[s]; ok && j >= 0 {
xi = append(xi, i)
inv = append(inv, j)
}
}
J := inv
n := len(xi)
T := make([]int, n)
L := make([]int, n)
for i := range T {
T[i] = n + 1
}
for i := 0; i < n; i++ {
k := sort.Search(n, func(k int) bool {
return T[k] >= J[i]
})
T[k] = J[i]
L[i] = k + 1
}
k := 0
for _, v := range L {
if k < v {
k = v
}
}
seq := make([]pair, 2+k)
seq[1+k] = pair{len(x), len(y)}
lastj := n
for i := n - 1; i >= 0; i-- {
if L[i] == k && J[i] < lastj {
seq[k] = pair{xi[i], yi[J[i]]}
k--
}
}
seq[0] = pair{0, 0}
return seq
}
-3
View File
@@ -41,7 +41,6 @@ import (
_ "github.com/xtls/xray-core/proxy/freedom"
_ "github.com/xtls/xray-core/proxy/http"
_ "github.com/xtls/xray-core/proxy/loopback"
_ "github.com/xtls/xray-core/proxy/masque"
_ "github.com/xtls/xray-core/proxy/shadowsocks"
_ "github.com/xtls/xray-core/proxy/socks"
_ "github.com/xtls/xray-core/proxy/trojan"
@@ -55,14 +54,12 @@ import (
_ "github.com/xtls/xray-core/transport/internet/grpc"
_ "github.com/xtls/xray-core/transport/internet/httpupgrade"
_ "github.com/xtls/xray-core/transport/internet/kcp"
_ "github.com/xtls/xray-core/transport/internet/masque"
_ "github.com/xtls/xray-core/transport/internet/reality"
_ "github.com/xtls/xray-core/transport/internet/splithttp"
_ "github.com/xtls/xray-core/transport/internet/tcp"
_ "github.com/xtls/xray-core/transport/internet/tls"
_ "github.com/xtls/xray-core/transport/internet/udp"
_ "github.com/xtls/xray-core/transport/internet/websocket"
_ "github.com/xtls/xray-core/transport/internet/xdrive"
// Transport headers
_ "github.com/xtls/xray-core/transport/internet/headers/http"
+10 -15
View File
@@ -190,12 +190,6 @@ func (h *Handler) matchFinalRule(network net.Network, address net.Address, port
func (h *Handler) Init(config *Config, pm policy.Manager) error {
h.config = config
h.policyManager = pm
if h.usesDialerProxy { // freedom is not the final outbound, final rules do not apply
if len(config.FinalRules) > 0 {
errors.LogWarning(context.Background(), `The "finalRules" setting is ignored when "sockopt.dialerProxy" is set, since freedom is not the final outbound.`)
}
return nil
}
h.finalRules = make([]*FinalRule, 0, len(config.FinalRules))
for _, rc := range config.FinalRules {
rule, err := buildFinalRule(rc)
@@ -259,10 +253,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
ob.Name = "freedom"
ob.CanSpliceCopy = 1
inbound := session.InboundFromContext(ctx)
var defaultRule *FinalRule
if !h.usesDialerProxy { // freedom is not the final outbound, final rules do not apply (and the domain is not resolved)
defaultRule = getDefaultFinalRule(inbound)
}
defaultRule := getDefaultFinalRule(inbound)
destination := ob.Target
origTargetAddr := ob.OriginalTarget.Address
@@ -351,11 +342,15 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
return h.blackhole(ctx, input, output, blockedRule, blockedDest)
}
if destination.Address.Family().IsDomain() && (defaultRule != nil || len(h.finalRules) > 0) {
// pre-check may fail or dialer may select another IP
remoteDest := net.DestinationFromAddr(conn.RemoteAddr())
if rule := h.matchFinalRule(remoteDest.Network, remoteDest.Address, remoteDest.Port, defaultRule); rule != nil && rule.action == RuleAction_Block {
conn.Close()
return h.blackhole(ctx, input, output, rule, &remoteDest)
if h.usesDialerProxy {
errors.LogInfo(ctx, "skipping final rule check for proxied remote endpoint, original target: ", destination)
} else {
// pre-check may fail or dialer may select another IP
remoteDest := net.DestinationFromAddr(conn.RemoteAddr())
if rule := h.matchFinalRule(remoteDest.Network, remoteDest.Address, remoteDest.Port, defaultRule); rule != nil && rule.action == RuleAction_Block {
conn.Close()
return h.blackhole(ctx, input, output, rule, &remoteDest)
}
}
}
+3 -3
View File
@@ -236,14 +236,14 @@ type UDPReader struct {
func (r *UDPReader) ReadFrom(p []byte) (n int, addr *net.Destination, err error) {
for {
var packet [1500]byte
var buf [hysteria.MaxDatagramFrameSize]byte
n, err := r.reader.Read(packet[:])
n, err := r.reader.Read(buf[:])
if err != nil {
return 0, nil, err
}
msg, err := ParseUDPMessage(packet[:n])
msg, err := ParseUDPMessage(buf[:n])
if err != nil {
continue
}
-328
View File
@@ -1,328 +0,0 @@
package masque
import (
"context"
go_errors "errors"
"io"
"net/netip"
"slices"
"sync"
"sync/atomic"
"time"
"golang.zx2c4.com/wireguard/tun"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/common/session"
"github.com/xtls/xray-core/common/signal"
"github.com/xtls/xray-core/common/task"
"github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/features/policy"
"github.com/xtls/xray-core/proxy/wireguard"
"github.com/xtls/xray-core/transport"
"github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/masque"
"github.com/xtls/xray-core/transport/internet/stat"
"github.com/xtls/xray-core/transport/internet/tls"
)
const (
establishTimeout = 10 * time.Second
retryInterval = time.Second
)
type Client struct {
server *protocol.ServerSpec
policyManager policy.Manager
remoteDNS []netip.Addr
ctx context.Context
cancel context.CancelFunc
tunnel atomic.Pointer[tunnel]
mu sync.Mutex
lastErr error
lastErrAt time.Time
}
func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) {
v := core.MustFromContext(ctx)
p := v.GetFeature(policy.ManagerType()).(policy.Manager)
streamSettings := session.StreamSettingsFromContext(ctx).(*internet.MemoryStreamConfig)
if _, ok := streamSettings.ProtocolSettings.(*masque.Config); !ok {
return nil, errors.New("not masque transport")
}
if tls.ConfigFromStreamSettings(streamSettings) == nil {
return nil, errors.New(`MASQUE requires "security": "tls"`)
}
if config.Server == nil {
return nil, errors.New(`no target server found`)
}
server, err := protocol.NewServerSpecFromPB(config.Server)
if err != nil {
return nil, errors.New("failed to get server spec").Base(err)
}
dns := config.RemoteDns
if len(dns) == 0 {
dns = []string{"1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001"}
}
remoteDNS := make([]netip.Addr, 0, len(dns))
for _, s := range dns {
addr, err := netip.ParseAddr(s)
if err != nil {
return nil, errors.New("invalid remote DNS server ", s).Base(err)
}
remoteDNS = append(remoteDNS, addr)
}
c := &Client{
server: server,
policyManager: p,
remoteDNS: remoteDNS,
}
c.ctx, c.cancel = context.WithCancel(context.Background())
return c, nil
}
func (c *Client) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error {
outbounds := session.OutboundsFromContext(ctx)
ob := outbounds[len(outbounds)-1]
if !ob.Target.IsValid() {
return errors.New("target not specified")
}
ob.Name = "masque"
ob.CanSpliceCopy = 3
t, err := c.getTunnel(ctx, dialer)
if err != nil {
return errors.New("failed to establish CONNECT-IP tunnel").Base(err)
}
var newCtx context.Context
var newCancel context.CancelFunc
if session.TimeoutOnlyFromContext(ctx) {
newCtx, newCancel = context.WithCancel(context.Background())
}
sessionPolicy := c.policyManager.ForLevel(0)
ctx, cancel := context.WithCancel(ctx)
timer := signal.CancelAfterInactivity(ctx, func() {
cancel()
if newCancel != nil {
newCancel()
}
}, sessionPolicy.Timeouts.ConnectionIdle)
if newCtx != nil {
ctx = newCtx
}
var reader buf.Reader
var writer buf.Writer
switch ob.Target.Network {
case net.Network_TCP:
var conn net.Conn
var err error
if sessionPolicy.Timeouts.Handshake != 0 {
timeoutCtx, timeoutCancel := context.WithTimeout(ctx, sessionPolicy.Timeouts.Handshake)
conn, err = t.tnet.DialContext(timeoutCtx, "tcp", ob.Target.NetAddr())
timeoutCancel()
} else {
conn, err = t.tnet.Dial("tcp", ob.Target.NetAddr())
}
if err != nil {
return errors.New("failed to create TCP connection").Base(err)
}
defer conn.Close()
reader = buf.NewReader(conn)
writer = buf.NewWriter(conn)
case net.Network_UDP:
conn, err := t.tnet.Dial("udp", ob.Target.NetAddr())
if err != nil {
return errors.New("failed to create UDP connection").Base(err)
}
defer conn.Close()
uc := &wireguard.UDPConnClient{
PacketConn: conn.(*internet.PacketConnWrapper).PacketConn,
Dest: conn.RemoteAddr().(*net.UDPAddr),
}
reader = uc
writer = uc
default:
panic(ob.Target.Network)
}
requestFunc := func() error {
defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)
return buf.Copy(link.Reader, writer, buf.UpdateActivity(timer))
}
responseFunc := func() error {
defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)
return buf.Copy(reader, link.Writer, buf.UpdateActivity(timer))
}
responseDonePost := task.OnSuccess(responseFunc, task.Close(link.Writer))
if err := task.Run(ctx, requestFunc, responseDonePost); err != nil {
common.Interrupt(link.Reader)
common.Interrupt(link.Writer)
return errors.New("connection ends").Base(err)
}
return nil
}
func (c *Client) getTunnel(ctx context.Context, dialer internet.Dialer) (*tunnel, error) {
c.mu.Lock()
defer c.mu.Unlock()
if c.ctx.Err() != nil {
return nil, errors.New("closed")
}
if t := c.tunnel.Load(); t != nil {
select {
case <-t.done:
default:
return t, nil
}
}
if err := ctx.Err(); err != nil {
return nil, err
}
if c.lastErr != nil && time.Since(c.lastErrAt) < retryInterval {
return nil, c.lastErr
}
t, err := c.establish(ctx, dialer)
if err != nil {
c.lastErr, c.lastErrAt = err, time.Now()
return nil, err
}
c.lastErr = nil
c.tunnel.Store(t)
if c.ctx.Err() != nil {
if c.tunnel.CompareAndSwap(t, nil) {
t.close()
}
return nil, errors.New("closed")
}
return t, nil
}
func (c *Client) establish(ctx context.Context, dialer internet.Dialer) (*tunnel, error) {
ctx, cancel := context.WithTimeout(context.WithoutCancel(ctx), establishTimeout)
defer cancel()
defer context.AfterFunc(c.ctx, cancel)()
conn, err := dialer.Dial(ctx, c.server.Destination)
if err != nil {
return nil, err
}
mconn, ok := stat.TryUnwrapStatsConn(conn).(*masque.Conn)
if !ok {
conn.Close()
return nil, errors.New("not a CONNECT-IP connection")
}
t, err := newTunnel(conn, mconn.LocalAddrs(), c.remoteDNS)
if err != nil {
conn.Close()
return nil, err
}
errors.LogInfo(ctx, "MASQUE: tunnel established from ", mconn.LocalAddrs())
return t, nil
}
func (c *Client) Close() error {
c.cancel()
if t := c.tunnel.Swap(nil); t != nil {
t.close()
}
return nil
}
type tunnel struct {
conn stat.Connection
dev tun.Device
tnet *wireguard.Net
done chan struct{}
closeOnce sync.Once
}
func newTunnel(conn stat.Connection, local []netip.Addr, remoteDNS []netip.Addr) (*tunnel, error) {
var dns []netip.Addr
for _, addr := range remoteDNS {
if slices.ContainsFunc(local, func(l netip.Addr) bool { return l.Is4() == addr.Is4() }) {
dns = append(dns, addr)
}
}
if len(dns) == 0 {
errors.LogWarning(context.Background(), "MASQUE: no remote DNS server is reachable from the assigned addresses ", local, ", domain names will fail to resolve")
dns = remoteDNS
}
dev, tnet, _, err := wireguard.CreateNetTUN(local, dns, masque.MinPacketSize, true)
if err != nil {
return nil, err
}
t := &tunnel{
conn: conn,
dev: dev,
tnet: tnet,
done: make(chan struct{}),
}
go t.readFromTunnel()
go t.writeToTunnel()
return t, nil
}
func (t *tunnel) readFromTunnel() {
defer t.close()
b := make([]byte, buf.Size)
for {
n, err := t.conn.Read(b)
if err != nil {
if go_errors.Is(err, io.ErrShortBuffer) {
continue
}
errors.LogInfoInner(context.Background(), err, "MASQUE: tunnel closed")
return
}
t.dev.Write([][]byte{b[:n]}, 0)
}
}
func (t *tunnel) writeToTunnel() {
bufs := [][]byte{make([]byte, masque.MinPacketSize)}
sizes := []int{0}
for {
if _, err := t.dev.Read(bufs, sizes, 0); err != nil {
return
}
if _, err := t.conn.Write(bufs[0][:sizes[0]]); err != nil {
var ptb *masque.PacketTooBigError
if go_errors.As(err, &ptb) {
go t.dev.Write([][]byte{ptb.ICMP}, 0)
}
}
}
}
func (t *tunnel) close() {
t.closeOnce.Do(func() {
close(t.done)
t.conn.Close()
t.dev.Close()
})
}
func init() {
common.Must(common.RegisterConfig((*ClientConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
return NewClient(ctx, config.(*ClientConfig))
}))
}
-136
View File
@@ -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: proxy/masque/config.proto
package masque
import (
protocol "github.com/xtls/xray-core/common/protocol"
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 ClientConfig struct {
state protoimpl.MessageState `protogen:"open.v1"`
Server *protocol.ServerEndpoint `protobuf:"bytes,1,opt,name=server,proto3" json:"server,omitempty"`
RemoteDns []string `protobuf:"bytes,2,rep,name=remote_dns,json=remoteDns,proto3" json:"remote_dns,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ClientConfig) Reset() {
*x = ClientConfig{}
mi := &file_proxy_masque_config_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ClientConfig) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ClientConfig) ProtoMessage() {}
func (x *ClientConfig) ProtoReflect() protoreflect.Message {
mi := &file_proxy_masque_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 ClientConfig.ProtoReflect.Descriptor instead.
func (*ClientConfig) Descriptor() ([]byte, []int) {
return file_proxy_masque_config_proto_rawDescGZIP(), []int{0}
}
func (x *ClientConfig) GetServer() *protocol.ServerEndpoint {
if x != nil {
return x.Server
}
return nil
}
func (x *ClientConfig) GetRemoteDns() []string {
if x != nil {
return x.RemoteDns
}
return nil
}
var File_proxy_masque_config_proto protoreflect.FileDescriptor
const file_proxy_masque_config_proto_rawDesc = "" +
"\n" +
"\x19proxy/masque/config.proto\x12\x11xray.proxy.masque\x1a!common/protocol/server_spec.proto\"k\n" +
"\fClientConfig\x12<\n" +
"\x06server\x18\x01 \x01(\v2$.xray.common.protocol.ServerEndpointR\x06server\x12\x1d\n" +
"\n" +
"remote_dns\x18\x02 \x03(\tR\tremoteDnsBU\n" +
"\x15com.xray.proxy.masqueP\x01Z&github.com/xtls/xray-core/proxy/masque\xaa\x02\x11Xray.Proxy.Masqueb\x06proto3"
var (
file_proxy_masque_config_proto_rawDescOnce sync.Once
file_proxy_masque_config_proto_rawDescData []byte
)
func file_proxy_masque_config_proto_rawDescGZIP() []byte {
file_proxy_masque_config_proto_rawDescOnce.Do(func() {
file_proxy_masque_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proxy_masque_config_proto_rawDesc), len(file_proxy_masque_config_proto_rawDesc)))
})
return file_proxy_masque_config_proto_rawDescData
}
var file_proxy_masque_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_proxy_masque_config_proto_goTypes = []any{
(*ClientConfig)(nil), // 0: xray.proxy.masque.ClientConfig
(*protocol.ServerEndpoint)(nil), // 1: xray.common.protocol.ServerEndpoint
}
var file_proxy_masque_config_proto_depIdxs = []int32{
1, // 0: xray.proxy.masque.ClientConfig.server:type_name -> xray.common.protocol.ServerEndpoint
1, // [1:1] is the sub-list for method output_type
1, // [1:1] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
}
func init() { file_proxy_masque_config_proto_init() }
func file_proxy_masque_config_proto_init() {
if File_proxy_masque_config_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_masque_config_proto_rawDesc), len(file_proxy_masque_config_proto_rawDesc)),
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_proxy_masque_config_proto_goTypes,
DependencyIndexes: file_proxy_masque_config_proto_depIdxs,
MessageInfos: file_proxy_masque_config_proto_msgTypes,
}.Build()
File_proxy_masque_config_proto = out.File
file_proxy_masque_config_proto_goTypes = nil
file_proxy_masque_config_proto_depIdxs = nil
}
-14
View File
@@ -1,14 +0,0 @@
syntax = "proto3";
package xray.proxy.masque;
option csharp_namespace = "Xray.Proxy.Masque";
option go_package = "github.com/xtls/xray-core/proxy/masque";
option java_package = "com.xray.proxy.masque";
option java_multiple_files = true;
import "common/protocol/server_spec.proto";
message ClientConfig {
xray.common.protocol.ServerEndpoint server = 1;
repeated string remote_dns = 2;
}
+4 -33
View File
@@ -37,25 +37,6 @@ type Handler struct {
downlinkCounter stats.Counter
}
type tunUDPStatsWriter struct {
writer buf.Writer
counter stats.Counter
}
func (w *tunUDPStatsWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
for len(mb) > 0 {
remaining, packet := buf.SplitFirst(mb)
packetSize := packet.Len()
if err := w.writer.WriteMultiBuffer(buf.MultiBuffer{packet}); err != nil {
buf.ReleaseMulti(remaining)
return err
}
w.counter.Add(int64(packetSize))
mb = remaining
}
return nil
}
// ConnectionHandler interface with the only method that stack is going to push new connections to
type ConnectionHandler interface {
HandleConnection(conn net.Conn, destination net.Destination)
@@ -123,7 +104,7 @@ func (t *Handler) Start() error {
iface := updater.Get()
if iface == nil {
errors.LogInfo(context.Background(), "[tun] falied to set interface > iface == nil")
return errors.New("iface not found")
return nil
}
return c.Control(func(fd uintptr) {
addrPort, _ := netip.ParseAddrPort(address)
@@ -190,8 +171,7 @@ func (t *Handler) HandleConnection(conn net.Conn, destination net.Destination) {
return
}
source := net.DestinationFromAddr(remote)
isUDP := destination.Network == net.Network_UDP
if !isUDP && (t.uplinkCounter != nil || t.downlinkCounter != nil) {
if t.uplinkCounter != nil || t.downlinkCounter != nil {
conn = &stat.CounterConnection{
Connection: conn,
ReadCounter: t.uplinkCounter,
@@ -223,18 +203,9 @@ func (t *Handler) HandleConnection(conn net.Conn, destination net.Destination) {
})
errors.LogInfo(ctx, "processing from ", source, " to ", destination)
reader := &buf.TimeoutWrapperReader{Reader: buf.NewReader(conn)}
writer := buf.NewWriter(conn)
if isUDP {
reader.Counter = t.uplinkCounter
if t.downlinkCounter != nil {
writer = &tunUDPStatsWriter{writer: writer, counter: t.downlinkCounter}
}
}
link := &transport.Link{
Reader: reader,
Writer: writer,
Reader: &buf.TimeoutWrapperReader{Reader: buf.NewReader(conn)},
Writer: buf.NewWriter(conn),
}
if err := t.dispatcher.DispatchLink(ctx, destination, link); err != nil {
errors.LogError(ctx, errors.New("connection closed").Base(err))
+140 -125
View File
@@ -3,6 +3,7 @@ package wireguard
import (
"context"
"fmt"
gonet "net"
"net/netip"
"reflect"
"strings"
@@ -27,10 +28,14 @@ import (
"github.com/xtls/xray-core/features/stats"
"github.com/xtls/xray-core/transport"
"github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/finalmask"
"golang.zx2c4.com/wireguard/device"
)
type entry struct {
got []net.IP
time time.Time
}
type Handler struct {
conf *DeviceConfig
policyManager policy.Manager
@@ -44,6 +49,11 @@ type Handler struct {
tnet *Net
dev *device.Device
mu sync.Mutex
// TODO: cache cleanup loop
local bool
cache map[string]entry
cacheMu sync.Mutex
}
func NewClient(ctx context.Context, conf *DeviceConfig) (*Handler, error) {
@@ -99,10 +109,15 @@ func NewClient(ctx context.Context, conf *DeviceConfig) (*Handler, error) {
return nil, err
}
local := false
dns := conf.DNS
if len(dns) == 0 {
dns = []string{"1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001"}
}
if len(dns) == 1 && dns[0] == "local" {
local = true
dns = nil
}
dnses := make([]netip.Addr, 0, len(dns))
for _, dns := range dns {
dnses = append(dnses, netip.MustParseAddr(dns))
@@ -136,6 +151,9 @@ func NewClient(ctx context.Context, conf *DeviceConfig) (*Handler, error) {
tun: tun,
tnet: tnet,
local: local,
cache: make(map[string]entry),
}, nil
}
@@ -154,6 +172,22 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
return err
}
var addr netip.Addr
if ob.Target.Address.Family().IsDomain() {
ip, err := h.resolveRemote(ob.Target.Address.String())
if err != nil {
return errors.New("failed to resolve domain").Base(err)
}
addr, _ = netip.AddrFromSlice(ip)
} else {
addr, _ = netip.AddrFromSlice(ob.Target.Address.IP())
}
addrPort := netip.AddrPortFrom(addr, ob.Target.Port.Value())
if !addrPort.IsValid() {
return errors.New("invalid target ", ob.Target)
}
var newCtx context.Context
var newCancel context.CancelFunc
if session.TimeoutOnlyFromContext(ctx) {
@@ -182,10 +216,10 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
var err error
if sessionPolicy.Timeouts.Handshake != 0 {
timeoutCtx, timeoutCancel := context.WithTimeout(ctx, sessionPolicy.Timeouts.Handshake)
conn, err = h.tnet.DialContext(timeoutCtx, "tcp", ob.Target.NetAddr())
conn, err = h.tnet.DialContextTCPAddrPort(timeoutCtx, addrPort)
timeoutCancel()
} else {
conn, err = h.tnet.Dial("tcp", ob.Target.NetAddr())
conn, err = h.tnet.DialContextTCPAddrPort(ctx, addrPort)
}
if err != nil {
return errors.New("failed to create TCP connection").Base(err)
@@ -194,14 +228,15 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
reader = buf.NewReader(conn)
writer = buf.NewWriter(conn)
case net.Network_UDP:
conn, err := h.tnet.Dial("udp", ob.Target.NetAddr())
conn, err := h.tnet.DialUDPAddrPort(netip.AddrPort{}, addrPort)
if err != nil {
return errors.New("failed to create UDP connection").Base(err)
}
defer conn.Close()
c := &UDPConnClient{
PacketConn: conn.(*internet.PacketConnWrapper).PacketConn,
Dest: conn.RemoteAddr().(*net.UDPAddr),
c := &udpConnClient{
PacketConn: conn.(*internet.PacketConnWrapper).PacketConn,
resolveFunc: h.resolveRemote,
dest: gonet.UDPAddrFromAddrPort(addrPort),
}
reader = c
writer = c
@@ -258,26 +293,26 @@ func (h *Handler) init(ctx context.Context) error {
if err != nil {
return nil, err
}
conn, err := internet.DialSystem(ctx, dest, h.streamSettings.SocketSettings)
if err != nil {
return nil, err
}
var pktConn net.PacketConn
if h.streamSettings.FinalMask != nil {
conn, err := h.streamSettings.FinalMask.DialUDP(ctx, dest)
switch c := conn.(type) {
case *internet.PacketConnWrapper:
pktConn = c.PacketConn
case *cnc.Connection:
pktConn = &internet.FakePacketConn{Conn: c}
default:
panic(reflect.TypeOf(c))
}
if h.streamSettings.UdpmaskManager != nil {
newConn, err := h.streamSettings.UdpmaskManager.WrapPacketConnClient(pktConn)
if err != nil {
return nil, errors.New("failed to dial to dest").Base(err)
}
pktConn = conn.(*finalmask.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 *internet.PacketConnWrapper:
pktConn = c.PacketConn
case *cnc.Connection:
pktConn = &internet.FakePacketConn{Conn: c}
default:
panic(reflect.TypeOf(c))
pktConn.Close()
return nil, errors.New("mask err").Base(err)
}
pktConn = newConn
}
if h.uplinkCounter != nil || h.downlinkCounter != nil {
pktConn = &PacketCounterConnection{
@@ -336,54 +371,90 @@ func (h *Handler) init(ctx context.Context) error {
}
func (h *Handler) resolveLocal(host string) (net.IP, error) {
return h.resolveDomain(host, h.conf.DomainStrategy, func(host string) ([]net.IP, uint32, error) {
return h.dns.LookupIP(host, dns.IPOption{IPv4Enable: true, IPv6Enable: true})
})
}
func (h *Handler) resolveRemote(host string) (net.IP, error) {
return h.resolveDomain(host, h.conf.DomainStrategy, func(host string) ([]net.IP, uint32, error) {
if h.local {
return h.dns.LookupIP(host, dns.IPOption{IPv4Enable: true, IPv6Enable: true})
}
return h.tnet.LookupHost(host)
})
}
func (h *Handler) resolveDomain(host string, strategy DeviceConfig_DomainStrategy, lookupIP func(host string) ([]net.IP, uint32, error)) (net.IP, error) {
if ip := net.ParseIP(host); ip != nil {
return ip, nil
}
ips, _, err := h.dns.LookupIP(host, dns.IPOption{IPv4Enable: true, IPv6Enable: true})
h.cacheMu.Lock()
if entry, ok := h.cache[host]; ok {
if time.Now().Before(entry.time) {
h.cacheMu.Unlock()
return entry.got[dice.Roll(len(entry.got))], nil
}
delete(h.cache, host)
}
h.cacheMu.Unlock()
ips, ttl, err := lookupIP(host)
if err != nil {
return nil, err
}
got := ips
if h.streamSettings.SocketSettings != nil {
var got4, got6 []net.IP
for _, ip := range ips {
if ip.To4() != nil {
got4 = append(got4, ip)
} else {
got6 = append(got6, ip)
}
}
switch h.streamSettings.SocketSettings.DomainStrategy {
case internet.DomainStrategy_AS_IS, internet.DomainStrategy_USE_IP, internet.DomainStrategy_FORCE_IP:
got = ips
case internet.DomainStrategy_USE_IP4, internet.DomainStrategy_FORCE_IP4:
got = got4
case internet.DomainStrategy_USE_IP6, internet.DomainStrategy_FORCE_IP6:
got = got6
case internet.DomainStrategy_USE_IP46, internet.DomainStrategy_FORCE_IP46:
got = got4
if len(got) == 0 {
got = got6
}
case internet.DomainStrategy_USE_IP64, internet.DomainStrategy_FORCE_IP64:
got = got6
if len(got) == 0 {
got = got4
}
}
if len(got) == 0 {
return nil, dns.ErrEmptyResponse
if len(ips) == 0 {
return nil, dns.ErrEmptyResponse
}
var got4, got6 []net.IP
for _, ip := range ips {
if ip.To4() != nil {
got4 = append(got4, ip)
} else {
got6 = append(got6, ip)
}
}
var got []net.IP
switch strategy {
case DeviceConfig_FORCE_IP:
got = ips
return ips[dice.Roll(len(ips))], nil
case DeviceConfig_FORCE_IP4:
got = got4
case DeviceConfig_FORCE_IP6:
got = got6
case DeviceConfig_FORCE_IP46:
got = got4
if len(got) == 0 {
got = got6
}
case DeviceConfig_FORCE_IP64:
got = got6
if len(got) == 0 {
got = got4
}
default:
panic(strategy)
}
if len(got) == 0 {
return nil, dns.ErrEmptyResponse
}
entry := entry{
got: got,
time: time.Now().Add(time.Duration(ttl) * time.Second),
}
h.cacheMu.Lock()
h.cache[host] = entry
h.cacheMu.Unlock()
return got[dice.Roll(len(got))], nil
}
type UDPConnClient struct {
type udpConnClient struct {
net.PacketConn
Dest *net.UDPAddr
resolveFunc func(host string) (net.IP, error)
dest *net.UDPAddr
}
func (c *UDPConnClient) ReadMultiBuffer() (buf.MultiBuffer, error) {
func (c *udpConnClient) ReadMultiBuffer() (buf.MultiBuffer, error) {
b := buf.New()
b.Resize(0, buf.Size)
n, addr, err := c.PacketConn.ReadFrom(b.Bytes())
@@ -402,13 +473,20 @@ func (c *UDPConnClient) ReadMultiBuffer() (buf.MultiBuffer, error) {
return buf.MultiBuffer{b}, nil
}
func (c *UDPConnClient) WriteMultiBuffer(mb buf.MultiBuffer) error {
func (c *udpConnClient) WriteMultiBuffer(mb buf.MultiBuffer) error {
for i, b := range mb {
dst := c.Dest
dst := c.dest
if b.UDP != nil {
if b.UDP.Address.Family().IsDomain() {
if b.UDP.Port != net.Port(dst.Port) {
dst = &net.UDPAddr{IP: dst.IP, Port: int(b.UDP.Port)}
ip, err := c.resolveFunc(b.UDP.Address.String())
if err != nil {
errors.LogErrorInner(context.Background(), err, "drop packet to ", b.UDP, " with size ", len(b.Bytes()))
b.Release()
continue
}
dst = &net.UDPAddr{
IP: ip,
Port: int(b.UDP.Port),
}
} else {
dst = b.UDP.RawNetAddr().(*net.UDPAddr)
@@ -445,66 +523,3 @@ func (c *PacketCounterConnection) WriteTo(p []byte, addr net.Addr) (n int, err e
}
return
}
type entry struct {
saddr []string
deadline time.Time
}
type cache struct {
running bool
m map[string]entry
mu sync.Mutex
}
func (c *cache) run() {
if c.running {
return
}
c.running = true
if c.m == nil {
c.m = make(map[string]entry)
}
go c.gc()
}
func (c *cache) gc() {
ticker := time.NewTicker(time.Minute)
defer ticker.Stop()
for now := range ticker.C {
c.mu.Lock()
for key, entry := range c.m {
if now.After(entry.deadline) {
delete(c.m, key)
}
}
if len(c.m) == 0 {
c.running = false
c.mu.Unlock()
return
}
c.mu.Unlock()
}
}
func (c *cache) LookupHost(host string) []string {
c.mu.Lock()
defer c.mu.Unlock()
c.run()
if entry, ok := c.m[host]; ok {
if time.Now().Before(entry.deadline) {
return entry.saddr
}
delete(c.m, host)
}
return nil
}
func (c *cache) Cache(host string, saddr []string, ttl uint32) {
c.mu.Lock()
defer c.mu.Unlock()
c.m[host] = entry{
saddr: saddr,
deadline: time.Now().Add(time.Second * time.Duration(ttl)),
}
}
+102 -26
View File
@@ -22,6 +22,61 @@ const (
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type DeviceConfig_DomainStrategy int32
const (
DeviceConfig_FORCE_IP DeviceConfig_DomainStrategy = 0
DeviceConfig_FORCE_IP4 DeviceConfig_DomainStrategy = 1
DeviceConfig_FORCE_IP6 DeviceConfig_DomainStrategy = 2
DeviceConfig_FORCE_IP46 DeviceConfig_DomainStrategy = 3
DeviceConfig_FORCE_IP64 DeviceConfig_DomainStrategy = 4
)
// Enum value maps for DeviceConfig_DomainStrategy.
var (
DeviceConfig_DomainStrategy_name = map[int32]string{
0: "FORCE_IP",
1: "FORCE_IP4",
2: "FORCE_IP6",
3: "FORCE_IP46",
4: "FORCE_IP64",
}
DeviceConfig_DomainStrategy_value = map[string]int32{
"FORCE_IP": 0,
"FORCE_IP4": 1,
"FORCE_IP6": 2,
"FORCE_IP46": 3,
"FORCE_IP64": 4,
}
)
func (x DeviceConfig_DomainStrategy) Enum() *DeviceConfig_DomainStrategy {
p := new(DeviceConfig_DomainStrategy)
*p = x
return p
}
func (x DeviceConfig_DomainStrategy) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (DeviceConfig_DomainStrategy) Descriptor() protoreflect.EnumDescriptor {
return file_proxy_wireguard_config_proto_enumTypes[0].Descriptor()
}
func (DeviceConfig_DomainStrategy) Type() protoreflect.EnumType {
return &file_proxy_wireguard_config_proto_enumTypes[0]
}
func (x DeviceConfig_DomainStrategy) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use DeviceConfig_DomainStrategy.Descriptor instead.
func (DeviceConfig_DomainStrategy) EnumDescriptor() ([]byte, []int) {
return file_proxy_wireguard_config_proto_rawDescGZIP(), []int{1, 0}
}
type PeerConfig struct {
state protoimpl.MessageState `protogen:"open.v1"`
PublicKey string `protobuf:"bytes,1,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"`
@@ -99,18 +154,19 @@ func (x *PeerConfig) GetAllowedIps() []string {
}
type DeviceConfig struct {
state protoimpl.MessageState `protogen:"open.v1"`
SecretKey string `protobuf:"bytes,1,opt,name=secret_key,json=secretKey,proto3" json:"secret_key,omitempty"`
Endpoint []string `protobuf:"bytes,2,rep,name=endpoint,proto3" json:"endpoint,omitempty"`
Peers []*PeerConfig `protobuf:"bytes,3,rep,name=peers,proto3" json:"peers,omitempty"`
Users []*protocol.User `protobuf:"bytes,5,rep,name=users,proto3" json:"users,omitempty"`
Mtu int32 `protobuf:"varint,4,opt,name=mtu,proto3" json:"mtu,omitempty"`
Reserved []byte `protobuf:"bytes,6,opt,name=reserved,proto3" json:"reserved,omitempty"`
IsClient bool `protobuf:"varint,8,opt,name=is_client,json=isClient,proto3" json:"is_client,omitempty"`
NoKernelTun bool `protobuf:"varint,9,opt,name=no_kernel_tun,json=noKernelTun,proto3" json:"no_kernel_tun,omitempty"`
DNS []string `protobuf:"bytes,10,rep,name=DNS,proto3" json:"DNS,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
SecretKey string `protobuf:"bytes,1,opt,name=secret_key,json=secretKey,proto3" json:"secret_key,omitempty"`
Endpoint []string `protobuf:"bytes,2,rep,name=endpoint,proto3" json:"endpoint,omitempty"`
Peers []*PeerConfig `protobuf:"bytes,3,rep,name=peers,proto3" json:"peers,omitempty"`
Users []*protocol.User `protobuf:"bytes,5,rep,name=users,proto3" json:"users,omitempty"`
Mtu int32 `protobuf:"varint,4,opt,name=mtu,proto3" json:"mtu,omitempty"`
Reserved []byte `protobuf:"bytes,6,opt,name=reserved,proto3" json:"reserved,omitempty"`
DomainStrategy DeviceConfig_DomainStrategy `protobuf:"varint,7,opt,name=domain_strategy,json=domainStrategy,proto3,enum=xray.proxy.wireguard.DeviceConfig_DomainStrategy" json:"domain_strategy,omitempty"`
IsClient bool `protobuf:"varint,8,opt,name=is_client,json=isClient,proto3" json:"is_client,omitempty"`
NoKernelTun bool `protobuf:"varint,9,opt,name=no_kernel_tun,json=noKernelTun,proto3" json:"no_kernel_tun,omitempty"`
DNS []string `protobuf:"bytes,10,rep,name=DNS,proto3" json:"DNS,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *DeviceConfig) Reset() {
@@ -185,6 +241,13 @@ func (x *DeviceConfig) GetReserved() []byte {
return nil
}
func (x *DeviceConfig) GetDomainStrategy() DeviceConfig_DomainStrategy {
if x != nil {
return x.DomainStrategy
}
return DeviceConfig_FORCE_IP
}
func (x *DeviceConfig) GetIsClient() bool {
if x != nil {
return x.IsClient
@@ -220,7 +283,7 @@ const file_proxy_wireguard_config_proto_rawDesc = "" +
"\n" +
"keep_alive\x18\x04 \x01(\tR\tkeepAlive\x12\x1f\n" +
"\vallowed_ips\x18\x05 \x03(\tR\n" +
"allowedIps\"\xb4\x02\n" +
"allowedIps\"\xee\x03\n" +
"\fDeviceConfig\x12\x1d\n" +
"\n" +
"secret_key\x18\x01 \x01(\tR\tsecretKey\x12\x1a\n" +
@@ -228,11 +291,20 @@ const file_proxy_wireguard_config_proto_rawDesc = "" +
"\x05peers\x18\x03 \x03(\v2 .xray.proxy.wireguard.PeerConfigR\x05peers\x120\n" +
"\x05users\x18\x05 \x03(\v2\x1a.xray.common.protocol.UserR\x05users\x12\x10\n" +
"\x03mtu\x18\x04 \x01(\x05R\x03mtu\x12\x1a\n" +
"\breserved\x18\x06 \x01(\fR\breserved\x12\x1b\n" +
"\breserved\x18\x06 \x01(\fR\breserved\x12Z\n" +
"\x0fdomain_strategy\x18\a \x01(\x0e21.xray.proxy.wireguard.DeviceConfig.DomainStrategyR\x0edomainStrategy\x12\x1b\n" +
"\tis_client\x18\b \x01(\bR\bisClient\x12\"\n" +
"\rno_kernel_tun\x18\t \x01(\bR\vnoKernelTun\x12\x10\n" +
"\x03DNS\x18\n" +
" \x03(\tR\x03DNSB^\n" +
" \x03(\tR\x03DNS\"\\\n" +
"\x0eDomainStrategy\x12\f\n" +
"\bFORCE_IP\x10\x00\x12\r\n" +
"\tFORCE_IP4\x10\x01\x12\r\n" +
"\tFORCE_IP6\x10\x02\x12\x0e\n" +
"\n" +
"FORCE_IP46\x10\x03\x12\x0e\n" +
"\n" +
"FORCE_IP64\x10\x04B^\n" +
"\x18com.xray.proxy.wireguardP\x01Z)github.com/xtls/xray-core/proxy/wireguard\xaa\x02\x14Xray.Proxy.WireGuardb\x06proto3"
var (
@@ -247,20 +319,23 @@ func file_proxy_wireguard_config_proto_rawDescGZIP() []byte {
return file_proxy_wireguard_config_proto_rawDescData
}
var file_proxy_wireguard_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_proxy_wireguard_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_proxy_wireguard_config_proto_goTypes = []any{
(*PeerConfig)(nil), // 0: xray.proxy.wireguard.PeerConfig
(*DeviceConfig)(nil), // 1: xray.proxy.wireguard.DeviceConfig
(*protocol.User)(nil), // 2: xray.common.protocol.User
(DeviceConfig_DomainStrategy)(0), // 0: xray.proxy.wireguard.DeviceConfig.DomainStrategy
(*PeerConfig)(nil), // 1: xray.proxy.wireguard.PeerConfig
(*DeviceConfig)(nil), // 2: xray.proxy.wireguard.DeviceConfig
(*protocol.User)(nil), // 3: xray.common.protocol.User
}
var file_proxy_wireguard_config_proto_depIdxs = []int32{
0, // 0: xray.proxy.wireguard.DeviceConfig.peers:type_name -> xray.proxy.wireguard.PeerConfig
2, // 1: xray.proxy.wireguard.DeviceConfig.users:type_name -> xray.common.protocol.User
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
1, // 0: xray.proxy.wireguard.DeviceConfig.peers:type_name -> xray.proxy.wireguard.PeerConfig
3, // 1: xray.proxy.wireguard.DeviceConfig.users:type_name -> xray.common.protocol.User
0, // 2: xray.proxy.wireguard.DeviceConfig.domain_strategy:type_name -> xray.proxy.wireguard.DeviceConfig.DomainStrategy
3, // [3:3] is the sub-list for method output_type
3, // [3:3] is the sub-list for method input_type
3, // [3:3] is the sub-list for extension type_name
3, // [3:3] is the sub-list for extension extendee
0, // [0:3] is the sub-list for field type_name
}
func init() { file_proxy_wireguard_config_proto_init() }
@@ -273,13 +348,14 @@ func file_proxy_wireguard_config_proto_init() {
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_wireguard_config_proto_rawDesc), len(file_proxy_wireguard_config_proto_rawDesc)),
NumEnums: 0,
NumEnums: 1,
NumMessages: 2,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_proxy_wireguard_config_proto_goTypes,
DependencyIndexes: file_proxy_wireguard_config_proto_depIdxs,
EnumInfos: file_proxy_wireguard_config_proto_enumTypes,
MessageInfos: file_proxy_wireguard_config_proto_msgTypes,
}.Build()
File_proxy_wireguard_config_proto = out.File
+8
View File
@@ -17,6 +17,13 @@ message PeerConfig {
}
message DeviceConfig {
enum DomainStrategy {
FORCE_IP = 0;
FORCE_IP4 = 1;
FORCE_IP6 = 2;
FORCE_IP46 = 3;
FORCE_IP64 = 4;
}
string secret_key = 1;
repeated string endpoint = 2;
repeated PeerConfig peers = 3;
@@ -24,6 +31,7 @@ message DeviceConfig {
int32 mtu = 4;
bytes reserved = 6;
DomainStrategy domain_strategy = 7;
bool is_client = 8;
bool no_kernel_tun = 9;
repeated string DNS = 10;
+14 -159
View File
@@ -15,8 +15,6 @@ import (
"net"
"net/netip"
"os"
"regexp"
"strconv"
"strings"
"syscall"
"time"
@@ -44,7 +42,6 @@ type netTun struct {
events chan tun.Event
notifyHandle *channel.NotificationHandle
incomingPacket chan *buffer.View
closed chan struct{}
mtu int
dnsServers []netip.Addr
hasV4, hasV6 bool
@@ -61,7 +58,6 @@ func CreateNetTUN(localAddresses, dnsServers []netip.Addr, mtu int, handleLocal
stack: stack.New(opts),
events: make(chan tun.Event, 10),
incomingPacket: make(chan *buffer.View),
closed: make(chan struct{}),
dnsServers: dnsServers,
mtu: mtu,
}
@@ -128,15 +124,12 @@ func (tun *netTun) Events() <-chan tun.Event {
}
func (tun *netTun) Read(buf [][]byte, sizes []int, offset int) (int, error) {
var view *buffer.View
select {
case view = <-tun.incomingPacket:
case <-tun.closed:
view, ok := <-tun.incomingPacket
if !ok {
return 0, os.ErrClosed
}
n, err := view.Read(buf[0][offset:])
view.Release()
if err != nil {
return 0, err
}
@@ -173,11 +166,7 @@ func (tun *netTun) WriteNotify() {
view := pkt.ToView()
pkt.DecRef()
select {
case tun.incomingPacket <- view:
case <-tun.closed:
view.Release()
}
tun.incomingPacket <- view
}
func (tun *netTun) Close() error {
@@ -190,9 +179,8 @@ func (tun *netTun) Close() error {
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)
if tun.incomingPacket != nil {
close(tun.incomingPacket)
}
return nil
@@ -231,7 +219,6 @@ type Net struct {
DialUDPAddrPort func(laddr, raddr netip.AddrPort) (net.Conn, error)
dnsServers []netip.Addr
hasV4, hasV6 bool
cache cache
}
func convertToFullAddr(endpoint netip.AddrPort) (tcpip.FullAddress, tcpip.NetworkProtocolNumber) {
@@ -259,12 +246,9 @@ var (
errServerTemporarilyMisbehaving = errors.New("server misbehaving")
errCanceled = errors.New("operation was canceled")
errTimeout = errors.New("i/o timeout")
errNumericPort = errors.New("port must be numeric")
errNoSuitableAddress = errors.New("no suitable address found")
errMissingAddress = errors.New("missing address")
)
func (net *Net) LookupHost(host string) (addrs []string, err error) {
func (net *Net) LookupHost(host string) (addrs []net.IP, ttl uint32, err error) {
return net.LookupContextHost(context.Background(), host)
}
@@ -583,12 +567,9 @@ func (tnet *Net) tryOneName(ctx context.Context, name string, qtype dnsmessage.T
return dnsmessage.Parser{}, "", lastErr
}
func (tnet *Net) LookupContextHost(ctx context.Context, host string) ([]string, error) {
if saddr := tnet.cache.LookupHost(host); saddr != nil {
return saddr, nil
}
func (tnet *Net) LookupContextHost(ctx context.Context, host string) ([]net.IP, uint32, error) {
if host == "" || (!tnet.hasV6 && !tnet.hasV4) {
return nil, &net.DNSError{Err: errNoSuchHost.Error(), Name: host, IsNotFound: true}
return nil, 0, &net.DNSError{Err: errNoSuchHost.Error(), Name: host, IsNotFound: true}
}
zlen := len(host)
if strings.IndexByte(host, ':') != -1 {
@@ -597,11 +578,11 @@ func (tnet *Net) LookupContextHost(ctx context.Context, host string) ([]string,
}
}
if ip, err := netip.ParseAddr(host[:zlen]); err == nil {
return []string{ip.String()}, nil
return []net.IP{ip.AsSlice()}, 0, nil
}
if !isDomainName(host) {
return nil, &net.DNSError{Err: errNoSuchHost.Error(), Name: host, IsNotFound: true}
return nil, 0, &net.DNSError{Err: errNoSuchHost.Error(), Name: host, IsNotFound: true}
}
type result struct {
p dnsmessage.Parser
@@ -702,137 +683,11 @@ func (tnet *Net) LookupContextHost(ctx context.Context, host string) ([]string,
}
if len(addrs) == 0 && lastErr != nil {
return nil, lastErr
return nil, 0, lastErr
}
saddrs := make([]string, 0, len(addrs))
ips := make([]net.IP, 0, len(addrs))
for _, ip := range addrs {
saddrs = append(saddrs, ip.String())
ips = append(ips, ip.AsSlice())
}
tnet.cache.Cache(host, saddrs, ttl)
return saddrs, nil
}
func partialDeadline(now, deadline time.Time, addrsRemaining int) (time.Time, error) {
if deadline.IsZero() {
return deadline, nil
}
timeRemaining := deadline.Sub(now)
if timeRemaining <= 0 {
return time.Time{}, errTimeout
}
timeout := timeRemaining / time.Duration(addrsRemaining)
const saneMinimum = 2 * time.Second
if timeout < saneMinimum {
if timeRemaining < saneMinimum {
timeout = timeRemaining
} else {
timeout = saneMinimum
}
}
return now.Add(timeout), nil
}
var protoSplitter = regexp.MustCompile(`^(tcp|udp|ping)(4|6)?$`)
func (tnet *Net) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
if ctx == nil {
panic("nil context")
}
var acceptV4, acceptV6 bool
matches := protoSplitter.FindStringSubmatch(network)
if matches == nil {
return nil, &net.OpError{Op: "dial", Err: net.UnknownNetworkError(network)}
} else if len(matches[2]) == 0 {
acceptV4 = true
acceptV6 = true
} else {
acceptV4 = matches[2][0] == '4'
acceptV6 = !acceptV4
}
var host string
var port int
if matches[1] == "ping" {
host = address
} else {
var sport string
var err error
host, sport, err = net.SplitHostPort(address)
if err != nil {
return nil, &net.OpError{Op: "dial", Err: err}
}
port, err = strconv.Atoi(sport)
if err != nil || port < 0 || port > 65535 {
return nil, &net.OpError{Op: "dial", Err: errNumericPort}
}
}
allAddr, err := tnet.LookupContextHost(ctx, host)
if err != nil {
return nil, &net.OpError{Op: "dial", Err: err}
}
var addrs []netip.AddrPort
for _, addr := range allAddr {
ip, err := netip.ParseAddr(addr)
if err == nil && ((ip.Is4() && acceptV4) || (ip.Is6() && acceptV6)) {
addrs = append(addrs, netip.AddrPortFrom(ip, uint16(port)))
}
}
if len(addrs) == 0 && len(allAddr) != 0 {
return nil, &net.OpError{Op: "dial", Err: errNoSuitableAddress}
}
var firstErr error
for i, addr := range addrs {
select {
case <-ctx.Done():
err := ctx.Err()
if err == context.Canceled {
err = errCanceled
} else if err == context.DeadlineExceeded {
err = errTimeout
}
return nil, &net.OpError{Op: "dial", Err: err}
default:
}
dialCtx := ctx
if deadline, hasDeadline := ctx.Deadline(); hasDeadline {
partialDeadline, err := partialDeadline(time.Now(), deadline, len(addrs)-i)
if err != nil {
if firstErr == nil {
firstErr = &net.OpError{Op: "dial", Err: err}
}
break
}
if partialDeadline.Before(deadline) {
var cancel context.CancelFunc
dialCtx, cancel = context.WithDeadline(ctx, partialDeadline)
defer cancel()
}
}
var c net.Conn
switch matches[1] {
case "tcp":
c, err = tnet.DialContextTCPAddrPort(dialCtx, addr)
case "udp":
c, err = tnet.DialUDPAddrPort(netip.AddrPort{}, addr)
case "ping":
err = errors.New("not support")
// c, err = tnet.DialPingAddr(netip.Addr{}, addr.Addr())
}
if err == nil {
return c, nil
}
if firstErr == nil {
firstErr = err
}
}
if firstErr == nil {
firstErr = &net.OpError{Op: "dial", Err: errMissingAddress}
}
return nil, firstErr
}
func (tnet *Net) Dial(network, address string) (net.Conn, error) {
return tnet.DialContext(context.Background(), network, address)
return ips, ttl, nil
}
+9 -7
View File
@@ -258,16 +258,18 @@ func (s *Server) Start() error {
return errors.New("address is domain")
}
listenFunc := func() (net.PacketConn, error) {
var pktConn net.PacketConn
var err error
if s.streamSettings.FinalMask != nil {
pktConn, err = s.streamSettings.FinalMask.ListenPacket(context.Background(), &net.UDPAddr{IP: s.src.Address.IP(), Port: int(s.src.Port)})
} else {
pktConn, err = internet.ListenSystemPacket(context.Background(), &net.UDPAddr{IP: s.src.Address.IP(), Port: int(s.src.Port)}, s.streamSettings.SocketSettings)
}
pktConn, err := internet.ListenSystemPacket(context.Background(), &net.UDPAddr{IP: s.src.Address.IP(), Port: int(s.src.Port)}, s.streamSettings.SocketSettings)
if err != nil {
return nil, err
}
if s.streamSettings.UdpmaskManager != nil {
newConn, err := s.streamSettings.UdpmaskManager.WrapPacketConnServer(pktConn)
if err != nil {
pktConn.Close()
return nil, errors.New("mask err").Base(err)
}
pktConn = newConn
}
if s.uplinkCounter != nil || s.downlinkCounter != nil {
pktConn = &PacketCounterConnection{
PacketConn: pktConn,
-275
View File
@@ -1,275 +0,0 @@
package scenarios
import (
"context"
gotls "crypto/tls"
"crypto/x509"
go_errors "errors"
"io"
"net/http"
"net/netip"
"sync/atomic"
"testing"
"time"
"github.com/apernet/quic-go"
"github.com/apernet/quic-go/http3"
"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/proxyman"
"github.com/xtls/xray-core/common"
clog "github.com/xtls/xray-core/common/log"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/common/protocol/tls/cert"
"github.com/xtls/xray-core/common/serial"
core "github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/proxy/dokodemo"
"github.com/xtls/xray-core/proxy/masque"
"github.com/xtls/xray-core/proxy/wireguard"
"github.com/xtls/xray-core/testing/servers/tcp"
"github.com/xtls/xray-core/testing/servers/udp"
"github.com/xtls/xray-core/transport/internet"
transmasque "github.com/xtls/xray-core/transport/internet/masque"
"github.com/xtls/xray-core/transport/internet/masque/connectip"
"github.com/xtls/xray-core/transport/internet/tls"
)
var (
masqueServerV4 = netip.MustParseAddr("10.13.0.1")
masqueServerV6 = netip.MustParseAddr("fd13::1")
masqueClientV4 = netip.MustParsePrefix("10.13.0.2/32")
masqueClientV6 = netip.MustParsePrefix("fd13::2/128")
)
const (
masqueEchoPort = 7
masqueAuthorization = "Basic dTpw"
)
func startMasqueServer(t *testing.T) (net.Port, [32]byte) {
dev, _, gstack, err := wireguard.CreateNetTUN([]netip.Addr{masqueServerV4, masqueServerV6}, nil, transmasque.MinPacketSize, false)
common.Must(err)
t.Cleanup(func() { dev.Close() })
for _, addr := range []netip.Addr{masqueServerV4, masqueServerV6} {
proto := ipv4.ProtocolNumber
if addr.Is6() {
proto = ipv6.ProtocolNumber
}
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 {
c, err := l.Accept()
if err != nil {
return
}
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
}
}
}()
}
}()
u, err := gonet.DialUDP(gstack, &local, nil, proto)
common.Must(err)
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]
go func() {
bufs := [][]byte{make([]byte, transmasque.MinPacketSize)}
sizes := []int{0}
for {
if _, err := dev.Read(bufs, sizes, 0); err != nil {
return
}
if conn := current.Load(); conn != nil {
if icmp, _ := conn.WritePacket(bufs[0][:sizes[0]]); len(icmp) > 0 {
go dev.Write([][]byte{icmp}, 0)
}
}
}
}()
handler := func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != transmasque.DefaultPath {
w.WriteHeader(http.StatusNotFound)
return
}
if r.Header.Get("Authorization") != masqueAuthorization {
w.WriteHeader(http.StatusUnauthorized)
return
}
req, err := connectip.ParseProxyRequest(r)
if err != nil {
var perr *connectip.ProxyRequestParseError
if go_errors.As(err, &perr) {
w.WriteHeader(perr.HTTPStatus)
}
return
}
conn, err := (&connectip.Proxy{}).Proxy(w, req)
if err != nil {
return
}
defer conn.Close()
common.Must(conn.AssignAddresses([]netip.Prefix{masqueClientV4, masqueClientV6}))
common.Must(conn.AdvertiseRoute([]connectip.IPRoute{
{StartIP: netip.IPv4Unspecified(), EndIP: netip.AddrFrom4([4]byte{255, 255, 255, 255})},
{StartIP: netip.IPv6Unspecified(), EndIP: netip.AddrFrom16([16]byte{0: 0xff, 1: 0xff, 2: 0xff, 3: 0xff, 4: 0xff, 5: 0xff, 6: 0xff, 7: 0xff, 8: 0xff, 9: 0xff, 10: 0xff, 11: 0xff, 12: 0xff, 13: 0xff, 14: 0xff, 15: 0xff})},
}))
go func() {
for {
ar, err := conn.ReceiveAddressRequest(context.Background())
if err != nil {
return
}
assigned := make([]netip.Prefix, len(ar.Prefixes))
for i, p := range ar.Prefixes {
if p.Addr().Is4() {
assigned[i] = masqueClientV4
} else {
assigned[i] = masqueClientV6
}
}
ar.Respond(assigned, nil)
}
}()
current.Store(conn)
b := make([]byte, 2048)
for {
n, err := conn.ReadPacket(b)
if err != nil {
if go_errors.Is(err, io.ErrShortBuffer) {
continue
}
return
}
dev.Write([][]byte{b[:n]}, 0)
}
}
certificate, certHash := cert.MustGenerate(nil, cert.CommonName("localhost"))
key := common.Must2(x509.ParsePKCS8PrivateKey(certificate.PrivateKey))
tlsConfig := &gotls.Config{
Certificates: []gotls.Certificate{{Certificate: [][]byte{certificate.Certificate}, PrivateKey: key}},
NextProtos: []string{http3.NextProtoH3},
}
pktConn := common.Must2(net.ListenUDP("udp", &net.UDPAddr{IP: net.LocalHostIP.IP()}))
tr := &quic.Transport{Conn: pktConn}
ln := common.Must2(tr.ListenEarly(tlsConfig, &quic.Config{EnableDatagrams: true, InitialPacketSize: 1350}))
server := &http3.Server{Handler: http.HandlerFunc(handler), EnableDatagrams: true}
go server.ServeListener(ln)
t.Cleanup(func() {
server.Close()
ln.Close()
tr.Close()
pktConn.Close()
})
return net.Port(pktConn.LocalAddr().(*net.UDPAddr).Port), certHash
}
func TestMasque(t *testing.T) {
serverPort, certHash := startMasqueServer(t)
tcpPort := tcp.PickPort()
tcp6Port := tcp.PickPort()
udpPort := udp.PickPort()
dokodemoTo := func(port net.Port, addr netip.Addr, network net.Network) *core.InboundHandlerConfig {
return &core.InboundHandlerConfig{
ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(port)}},
Listen: net.NewIPOrDomain(net.LocalHostIP),
}),
ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
RewriteAddress: net.NewIPOrDomain(net.IPAddress(addr.AsSlice())),
RewritePort: masqueEchoPort,
AllowedNetworks: []net.Network{network},
}),
}
}
clientConfig := &core.Config{
App: []*serial.TypedMessage{
serial.ToTypedMessage(&log.Config{
ErrorLogLevel: clog.Severity_Debug,
ErrorLogType: log.LogType_Console,
}),
},
Inbound: []*core.InboundHandlerConfig{
dokodemoTo(tcpPort, masqueServerV4, net.Network_TCP),
dokodemoTo(tcp6Port, masqueServerV6, net.Network_TCP),
dokodemoTo(udpPort, masqueServerV4, net.Network_UDP),
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&masque.ClientConfig{
Server: &protocol.ServerEndpoint{
Address: net.NewIPOrDomain(net.LocalHostIP),
Port: uint32(serverPort),
},
}),
SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
StreamSettings: &internet.StreamConfig{
ProtocolName: "masque",
TransportSettings: []*internet.TransportConfig{
{
ProtocolName: "masque",
Settings: serial.ToTypedMessage(&transmasque.Config{
Path: transmasque.DefaultPath,
Headers: map[string]string{"Authorization": masqueAuthorization},
}),
},
},
SecurityType: serial.GetMessageType(&tls.Config{}),
SecuritySettings: []*serial.TypedMessage{
serial.ToTypedMessage(&tls.Config{
ServerName: "localhost",
PinnedPeerCertSha256: [][]byte{certHash[:]},
}),
},
},
}),
},
},
}
servers, err := InitializeServerConfigs(clientConfig)
common.Must(err)
defer CloseAllServers(servers)
var errg errgroup.Group
for range 3 {
errg.Go(testTCPConn(tcpPort, 1024*1024, time.Second*20))
}
errg.Go(testTCPConn(tcp6Port, 1024*1024, time.Second*20))
errg.Go(testUDPConn(udpPort, 1024, time.Second*5))
if err := errg.Wait(); err != nil {
t.Error(err)
}
}
-2
View File
@@ -65,7 +65,6 @@ func TestWireguard(t *testing.T) {
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
}),
SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{}),
},
},
}
@@ -105,7 +104,6 @@ func TestWireguard(t *testing.T) {
AllowedIps: []string{"0.0.0.0/0", "::0/0"},
}},
}),
SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{}),
},
},
}
+138 -63
View File
@@ -206,7 +206,7 @@ func (x SocketConfig_TProxyMode) Number() protoreflect.EnumNumber {
// Deprecated: Use SocketConfig_TProxyMode.Descriptor instead.
func (SocketConfig_TProxyMode) EnumDescriptor() ([]byte, []int) {
return file_transport_internet_config_proto_rawDescGZIP(), []int{4, 0}
return file_transport_internet_config_proto_rawDescGZIP(), []int{5, 0}
}
type TransportConfig struct {
@@ -382,6 +382,66 @@ func (x *StreamConfig) GetSocketSettings() *SocketConfig {
return nil
}
type UdpHop struct {
state protoimpl.MessageState `protogen:"open.v1"`
Ports []uint32 `protobuf:"varint,1,rep,packed,name=ports,proto3" json:"ports,omitempty"`
IntervalMin int64 `protobuf:"varint,2,opt,name=interval_min,json=intervalMin,proto3" json:"interval_min,omitempty"`
IntervalMax int64 `protobuf:"varint,3,opt,name=interval_max,json=intervalMax,proto3" json:"interval_max,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *UdpHop) Reset() {
*x = UdpHop{}
mi := &file_transport_internet_config_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *UdpHop) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*UdpHop) ProtoMessage() {}
func (x *UdpHop) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_config_proto_msgTypes[2]
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 UdpHop.ProtoReflect.Descriptor instead.
func (*UdpHop) Descriptor() ([]byte, []int) {
return file_transport_internet_config_proto_rawDescGZIP(), []int{2}
}
func (x *UdpHop) GetPorts() []uint32 {
if x != nil {
return x.Ports
}
return nil
}
func (x *UdpHop) GetIntervalMin() int64 {
if x != nil {
return x.IntervalMin
}
return 0
}
func (x *UdpHop) GetIntervalMax() int64 {
if x != nil {
return x.IntervalMax
}
return 0
}
type QuicParams struct {
state protoimpl.MessageState `protogen:"open.v1"`
Congestion string `protobuf:"bytes,1,opt,name=congestion,proto3" json:"congestion,omitempty"`
@@ -389,24 +449,25 @@ type QuicParams struct {
BrutalUp uint64 `protobuf:"varint,3,opt,name=brutal_up,json=brutalUp,proto3" json:"brutal_up,omitempty"`
BrutalDown uint64 `protobuf:"varint,4,opt,name=brutal_down,json=brutalDown,proto3" json:"brutal_down,omitempty"`
BrutalDisableLossCompensation bool `protobuf:"varint,5,opt,name=brutal_disable_loss_compensation,json=brutalDisableLossCompensation,proto3" json:"brutal_disable_loss_compensation,omitempty"`
InitStreamReceiveWindow uint64 `protobuf:"varint,6,opt,name=init_stream_receive_window,json=initStreamReceiveWindow,proto3" json:"init_stream_receive_window,omitempty"`
MaxStreamReceiveWindow uint64 `protobuf:"varint,7,opt,name=max_stream_receive_window,json=maxStreamReceiveWindow,proto3" json:"max_stream_receive_window,omitempty"`
InitConnReceiveWindow uint64 `protobuf:"varint,8,opt,name=init_conn_receive_window,json=initConnReceiveWindow,proto3" json:"init_conn_receive_window,omitempty"`
MaxConnReceiveWindow uint64 `protobuf:"varint,9,opt,name=max_conn_receive_window,json=maxConnReceiveWindow,proto3" json:"max_conn_receive_window,omitempty"`
MaxIdleTimeout int64 `protobuf:"varint,10,opt,name=max_idle_timeout,json=maxIdleTimeout,proto3" json:"max_idle_timeout,omitempty"`
KeepAlivePeriod int64 `protobuf:"varint,11,opt,name=keep_alive_period,json=keepAlivePeriod,proto3" json:"keep_alive_period,omitempty"`
DisablePathMtuDiscovery bool `protobuf:"varint,12,opt,name=disable_path_mtu_discovery,json=disablePathMtuDiscovery,proto3" json:"disable_path_mtu_discovery,omitempty"`
DisableChromeParrot bool `protobuf:"varint,13,opt,name=disable_chrome_parrot,json=disableChromeParrot,proto3" json:"disable_chrome_parrot,omitempty"`
DisableGSO bool `protobuf:"varint,14,opt,name=disableGSO,proto3" json:"disableGSO,omitempty"`
MaxIncomingStreams int64 `protobuf:"varint,15,opt,name=max_incoming_streams,json=maxIncomingStreams,proto3" json:"max_incoming_streams,omitempty"`
DisableStatelessReset bool `protobuf:"varint,16,opt,name=disable_stateless_reset,json=disableStatelessReset,proto3" json:"disable_stateless_reset,omitempty"`
UdpHop *UdpHop `protobuf:"bytes,6,opt,name=udp_hop,json=udpHop,proto3" json:"udp_hop,omitempty"`
InitStreamReceiveWindow uint64 `protobuf:"varint,7,opt,name=init_stream_receive_window,json=initStreamReceiveWindow,proto3" json:"init_stream_receive_window,omitempty"`
MaxStreamReceiveWindow uint64 `protobuf:"varint,8,opt,name=max_stream_receive_window,json=maxStreamReceiveWindow,proto3" json:"max_stream_receive_window,omitempty"`
InitConnReceiveWindow uint64 `protobuf:"varint,9,opt,name=init_conn_receive_window,json=initConnReceiveWindow,proto3" json:"init_conn_receive_window,omitempty"`
MaxConnReceiveWindow uint64 `protobuf:"varint,10,opt,name=max_conn_receive_window,json=maxConnReceiveWindow,proto3" json:"max_conn_receive_window,omitempty"`
MaxIdleTimeout int64 `protobuf:"varint,11,opt,name=max_idle_timeout,json=maxIdleTimeout,proto3" json:"max_idle_timeout,omitempty"`
KeepAlivePeriod int64 `protobuf:"varint,12,opt,name=keep_alive_period,json=keepAlivePeriod,proto3" json:"keep_alive_period,omitempty"`
DisablePathMtuDiscovery bool `protobuf:"varint,13,opt,name=disable_path_mtu_discovery,json=disablePathMtuDiscovery,proto3" json:"disable_path_mtu_discovery,omitempty"`
DisableChromeParrot bool `protobuf:"varint,14,opt,name=disable_chrome_parrot,json=disableChromeParrot,proto3" json:"disable_chrome_parrot,omitempty"`
DisableGSO bool `protobuf:"varint,15,opt,name=disableGSO,proto3" json:"disableGSO,omitempty"`
MaxIncomingStreams int64 `protobuf:"varint,16,opt,name=max_incoming_streams,json=maxIncomingStreams,proto3" json:"max_incoming_streams,omitempty"`
DisableStatelessReset bool `protobuf:"varint,17,opt,name=disable_stateless_reset,json=disableStatelessReset,proto3" json:"disable_stateless_reset,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *QuicParams) Reset() {
*x = QuicParams{}
mi := &file_transport_internet_config_proto_msgTypes[2]
mi := &file_transport_internet_config_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -418,7 +479,7 @@ func (x *QuicParams) String() string {
func (*QuicParams) ProtoMessage() {}
func (x *QuicParams) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_config_proto_msgTypes[2]
mi := &file_transport_internet_config_proto_msgTypes[3]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -431,7 +492,7 @@ func (x *QuicParams) ProtoReflect() protoreflect.Message {
// Deprecated: Use QuicParams.ProtoReflect.Descriptor instead.
func (*QuicParams) Descriptor() ([]byte, []int) {
return file_transport_internet_config_proto_rawDescGZIP(), []int{2}
return file_transport_internet_config_proto_rawDescGZIP(), []int{3}
}
func (x *QuicParams) GetCongestion() string {
@@ -469,6 +530,13 @@ func (x *QuicParams) GetBrutalDisableLossCompensation() bool {
return false
}
func (x *QuicParams) GetUdpHop() *UdpHop {
if x != nil {
return x.UdpHop
}
return nil
}
func (x *QuicParams) GetInitStreamReceiveWindow() uint64 {
if x != nil {
return x.InitStreamReceiveWindow
@@ -560,7 +628,7 @@ type CustomSockopt struct {
func (x *CustomSockopt) Reset() {
*x = CustomSockopt{}
mi := &file_transport_internet_config_proto_msgTypes[3]
mi := &file_transport_internet_config_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -572,7 +640,7 @@ func (x *CustomSockopt) String() string {
func (*CustomSockopt) ProtoMessage() {}
func (x *CustomSockopt) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_config_proto_msgTypes[3]
mi := &file_transport_internet_config_proto_msgTypes[4]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -585,7 +653,7 @@ func (x *CustomSockopt) ProtoReflect() protoreflect.Message {
// Deprecated: Use CustomSockopt.ProtoReflect.Descriptor instead.
func (*CustomSockopt) Descriptor() ([]byte, []int) {
return file_transport_internet_config_proto_rawDescGZIP(), []int{3}
return file_transport_internet_config_proto_rawDescGZIP(), []int{4}
}
func (x *CustomSockopt) GetSystem() string {
@@ -665,7 +733,7 @@ type SocketConfig struct {
func (x *SocketConfig) Reset() {
*x = SocketConfig{}
mi := &file_transport_internet_config_proto_msgTypes[4]
mi := &file_transport_internet_config_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -677,7 +745,7 @@ func (x *SocketConfig) String() string {
func (*SocketConfig) ProtoMessage() {}
func (x *SocketConfig) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_config_proto_msgTypes[4]
mi := &file_transport_internet_config_proto_msgTypes[5]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -690,7 +758,7 @@ func (x *SocketConfig) ProtoReflect() protoreflect.Message {
// Deprecated: Use SocketConfig.ProtoReflect.Descriptor instead.
func (*SocketConfig) Descriptor() ([]byte, []int) {
return file_transport_internet_config_proto_rawDescGZIP(), []int{4}
return file_transport_internet_config_proto_rawDescGZIP(), []int{5}
}
func (x *SocketConfig) GetMark() int32 {
@@ -852,7 +920,7 @@ type HappyEyeballsConfig struct {
func (x *HappyEyeballsConfig) Reset() {
*x = HappyEyeballsConfig{}
mi := &file_transport_internet_config_proto_msgTypes[5]
mi := &file_transport_internet_config_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -864,7 +932,7 @@ func (x *HappyEyeballsConfig) String() string {
func (*HappyEyeballsConfig) ProtoMessage() {}
func (x *HappyEyeballsConfig) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_config_proto_msgTypes[5]
mi := &file_transport_internet_config_proto_msgTypes[6]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -877,7 +945,7 @@ func (x *HappyEyeballsConfig) ProtoReflect() protoreflect.Message {
// Deprecated: Use HappyEyeballsConfig.ProtoReflect.Descriptor instead.
func (*HappyEyeballsConfig) Descriptor() ([]byte, []int) {
return file_transport_internet_config_proto_rawDescGZIP(), []int{5}
return file_transport_internet_config_proto_rawDescGZIP(), []int{6}
}
func (x *HappyEyeballsConfig) GetPrioritizeIpv6() bool {
@@ -928,7 +996,11 @@ const file_transport_internet_config_proto_rawDesc = "" +
"\btcpmasks\x18\v \x03(\v2 .xray.common.serial.TypedMessageR\btcpmasks\x12D\n" +
"\vquic_params\x18\f \x01(\v2#.xray.transport.internet.QuicParamsR\n" +
"quicParams\x12N\n" +
"\x0fsocket_settings\x18\x06 \x01(\v2%.xray.transport.internet.SocketConfigR\x0esocketSettings\"\x8d\x06\n" +
"\x0fsocket_settings\x18\x06 \x01(\v2%.xray.transport.internet.SocketConfigR\x0esocketSettings\"d\n" +
"\x06UdpHop\x12\x14\n" +
"\x05ports\x18\x01 \x03(\rR\x05ports\x12!\n" +
"\finterval_min\x18\x02 \x01(\x03R\vintervalMin\x12!\n" +
"\finterval_max\x18\x03 \x01(\x03R\vintervalMax\"\xc7\x06\n" +
"\n" +
"QuicParams\x12\x1e\n" +
"\n" +
@@ -939,21 +1011,22 @@ const file_transport_internet_config_proto_rawDesc = "" +
"\tbrutal_up\x18\x03 \x01(\x04R\bbrutalUp\x12\x1f\n" +
"\vbrutal_down\x18\x04 \x01(\x04R\n" +
"brutalDown\x12G\n" +
" brutal_disable_loss_compensation\x18\x05 \x01(\bR\x1dbrutalDisableLossCompensation\x12;\n" +
"\x1ainit_stream_receive_window\x18\x06 \x01(\x04R\x17initStreamReceiveWindow\x129\n" +
"\x19max_stream_receive_window\x18\a \x01(\x04R\x16maxStreamReceiveWindow\x127\n" +
"\x18init_conn_receive_window\x18\b \x01(\x04R\x15initConnReceiveWindow\x125\n" +
"\x17max_conn_receive_window\x18\t \x01(\x04R\x14maxConnReceiveWindow\x12(\n" +
"\x10max_idle_timeout\x18\n" +
" \x01(\x03R\x0emaxIdleTimeout\x12*\n" +
"\x11keep_alive_period\x18\v \x01(\x03R\x0fkeepAlivePeriod\x12;\n" +
"\x1adisable_path_mtu_discovery\x18\f \x01(\bR\x17disablePathMtuDiscovery\x122\n" +
"\x15disable_chrome_parrot\x18\r \x01(\bR\x13disableChromeParrot\x12\x1e\n" +
" brutal_disable_loss_compensation\x18\x05 \x01(\bR\x1dbrutalDisableLossCompensation\x128\n" +
"\audp_hop\x18\x06 \x01(\v2\x1f.xray.transport.internet.UdpHopR\x06udpHop\x12;\n" +
"\x1ainit_stream_receive_window\x18\a \x01(\x04R\x17initStreamReceiveWindow\x129\n" +
"\x19max_stream_receive_window\x18\b \x01(\x04R\x16maxStreamReceiveWindow\x127\n" +
"\x18init_conn_receive_window\x18\t \x01(\x04R\x15initConnReceiveWindow\x125\n" +
"\x17max_conn_receive_window\x18\n" +
" \x01(\x04R\x14maxConnReceiveWindow\x12(\n" +
"\x10max_idle_timeout\x18\v \x01(\x03R\x0emaxIdleTimeout\x12*\n" +
"\x11keep_alive_period\x18\f \x01(\x03R\x0fkeepAlivePeriod\x12;\n" +
"\x1adisable_path_mtu_discovery\x18\r \x01(\bR\x17disablePathMtuDiscovery\x122\n" +
"\x15disable_chrome_parrot\x18\x0e \x01(\bR\x13disableChromeParrot\x12\x1e\n" +
"\n" +
"disableGSO\x18\x0e \x01(\bR\n" +
"disableGSO\x18\x0f \x01(\bR\n" +
"disableGSO\x120\n" +
"\x14max_incoming_streams\x18\x0f \x01(\x03R\x12maxIncomingStreams\x126\n" +
"\x17disable_stateless_reset\x18\x10 \x01(\bR\x15disableStatelessReset\"\x93\x01\n" +
"\x14max_incoming_streams\x18\x10 \x01(\x03R\x12maxIncomingStreams\x126\n" +
"\x17disable_stateless_reset\x18\x11 \x01(\bR\x15disableStatelessReset\"\x93\x01\n" +
"\rCustomSockopt\x12\x16\n" +
"\x06system\x18\x01 \x01(\tR\x06system\x12\x18\n" +
"\anetwork\x18\x02 \x01(\tR\anetwork\x12\x14\n" +
@@ -1037,39 +1110,41 @@ func file_transport_internet_config_proto_rawDescGZIP() []byte {
}
var file_transport_internet_config_proto_enumTypes = make([]protoimpl.EnumInfo, 3)
var file_transport_internet_config_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
var file_transport_internet_config_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
var file_transport_internet_config_proto_goTypes = []any{
(DomainStrategy)(0), // 0: xray.transport.internet.DomainStrategy
(AddressPortStrategy)(0), // 1: xray.transport.internet.AddressPortStrategy
(SocketConfig_TProxyMode)(0), // 2: xray.transport.internet.SocketConfig.TProxyMode
(*TransportConfig)(nil), // 3: xray.transport.internet.TransportConfig
(*StreamConfig)(nil), // 4: xray.transport.internet.StreamConfig
(*QuicParams)(nil), // 5: xray.transport.internet.QuicParams
(*CustomSockopt)(nil), // 6: xray.transport.internet.CustomSockopt
(*SocketConfig)(nil), // 7: xray.transport.internet.SocketConfig
(*HappyEyeballsConfig)(nil), // 8: xray.transport.internet.HappyEyeballsConfig
(*serial.TypedMessage)(nil), // 9: xray.common.serial.TypedMessage
(*net.IPOrDomain)(nil), // 10: xray.common.net.IPOrDomain
(*UdpHop)(nil), // 5: xray.transport.internet.UdpHop
(*QuicParams)(nil), // 6: xray.transport.internet.QuicParams
(*CustomSockopt)(nil), // 7: xray.transport.internet.CustomSockopt
(*SocketConfig)(nil), // 8: xray.transport.internet.SocketConfig
(*HappyEyeballsConfig)(nil), // 9: xray.transport.internet.HappyEyeballsConfig
(*serial.TypedMessage)(nil), // 10: xray.common.serial.TypedMessage
(*net.IPOrDomain)(nil), // 11: xray.common.net.IPOrDomain
}
var file_transport_internet_config_proto_depIdxs = []int32{
9, // 0: xray.transport.internet.TransportConfig.settings:type_name -> xray.common.serial.TypedMessage
10, // 1: xray.transport.internet.StreamConfig.address:type_name -> xray.common.net.IPOrDomain
10, // 0: xray.transport.internet.TransportConfig.settings:type_name -> xray.common.serial.TypedMessage
11, // 1: xray.transport.internet.StreamConfig.address:type_name -> xray.common.net.IPOrDomain
3, // 2: xray.transport.internet.StreamConfig.transport_settings:type_name -> xray.transport.internet.TransportConfig
9, // 3: xray.transport.internet.StreamConfig.security_settings:type_name -> xray.common.serial.TypedMessage
9, // 4: xray.transport.internet.StreamConfig.udpmasks:type_name -> xray.common.serial.TypedMessage
9, // 5: xray.transport.internet.StreamConfig.tcpmasks:type_name -> xray.common.serial.TypedMessage
5, // 6: xray.transport.internet.StreamConfig.quic_params:type_name -> xray.transport.internet.QuicParams
7, // 7: xray.transport.internet.StreamConfig.socket_settings:type_name -> xray.transport.internet.SocketConfig
2, // 8: xray.transport.internet.SocketConfig.tproxy:type_name -> xray.transport.internet.SocketConfig.TProxyMode
0, // 9: xray.transport.internet.SocketConfig.domain_strategy:type_name -> xray.transport.internet.DomainStrategy
6, // 10: xray.transport.internet.SocketConfig.customSockopt:type_name -> xray.transport.internet.CustomSockopt
1, // 11: xray.transport.internet.SocketConfig.address_port_strategy:type_name -> xray.transport.internet.AddressPortStrategy
8, // 12: xray.transport.internet.SocketConfig.happy_eyeballs:type_name -> xray.transport.internet.HappyEyeballsConfig
13, // [13:13] is the sub-list for method output_type
13, // [13:13] is the sub-list for method input_type
13, // [13:13] is the sub-list for extension type_name
13, // [13:13] is the sub-list for extension extendee
0, // [0:13] is the sub-list for field type_name
10, // 3: xray.transport.internet.StreamConfig.security_settings:type_name -> xray.common.serial.TypedMessage
10, // 4: xray.transport.internet.StreamConfig.udpmasks:type_name -> xray.common.serial.TypedMessage
10, // 5: xray.transport.internet.StreamConfig.tcpmasks:type_name -> xray.common.serial.TypedMessage
6, // 6: xray.transport.internet.StreamConfig.quic_params:type_name -> xray.transport.internet.QuicParams
8, // 7: xray.transport.internet.StreamConfig.socket_settings:type_name -> xray.transport.internet.SocketConfig
5, // 8: xray.transport.internet.QuicParams.udp_hop:type_name -> xray.transport.internet.UdpHop
2, // 9: xray.transport.internet.SocketConfig.tproxy:type_name -> xray.transport.internet.SocketConfig.TProxyMode
0, // 10: xray.transport.internet.SocketConfig.domain_strategy:type_name -> xray.transport.internet.DomainStrategy
7, // 11: xray.transport.internet.SocketConfig.customSockopt:type_name -> xray.transport.internet.CustomSockopt
1, // 12: xray.transport.internet.SocketConfig.address_port_strategy:type_name -> xray.transport.internet.AddressPortStrategy
9, // 13: xray.transport.internet.SocketConfig.happy_eyeballs:type_name -> xray.transport.internet.HappyEyeballsConfig
14, // [14:14] is the sub-list for method output_type
14, // [14:14] is the sub-list for method input_type
14, // [14:14] is the sub-list for extension type_name
14, // [14:14] is the sub-list for extension extendee
0, // [0:14] is the sub-list for field type_name
}
func init() { file_transport_internet_config_proto_init() }
@@ -1083,7 +1158,7 @@ func file_transport_internet_config_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_config_proto_rawDesc), len(file_transport_internet_config_proto_rawDesc)),
NumEnums: 3,
NumMessages: 6,
NumMessages: 7,
NumExtensions: 0,
NumServices: 0,
},
+18 -11
View File
@@ -64,23 +64,30 @@ message StreamConfig {
SocketConfig socket_settings = 6;
}
message UdpHop {
repeated uint32 ports = 1;
int64 interval_min = 2;
int64 interval_max = 3;
}
message QuicParams {
string congestion = 1;
string bbr_profile = 2;
uint64 brutal_up = 3;
uint64 brutal_down = 4;
bool brutal_disable_loss_compensation = 5;
uint64 init_stream_receive_window = 6;
uint64 max_stream_receive_window = 7;
uint64 init_conn_receive_window = 8;
uint64 max_conn_receive_window = 9;
int64 max_idle_timeout = 10;
int64 keep_alive_period = 11;
bool disable_path_mtu_discovery = 12;
bool disable_chrome_parrot = 13;
bool disableGSO = 14;
int64 max_incoming_streams = 15;
bool disable_stateless_reset = 16;
UdpHop udp_hop = 6;
uint64 init_stream_receive_window = 7;
uint64 max_stream_receive_window = 8;
uint64 init_conn_receive_window = 9;
uint64 max_conn_receive_window = 10;
int64 max_idle_timeout = 11;
int64 keep_alive_period = 12;
bool disable_path_mtu_discovery = 13;
bool disable_chrome_parrot = 14;
bool disableGSO = 15;
int64 max_incoming_streams = 16;
bool disable_stateless_reset = 17;
}
message CustomSockopt {
+100 -237
View File
@@ -2,291 +2,106 @@ package finalmask
import (
"context"
"fmt"
"net"
"slices"
"github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
)
type Dialer struct {
DialTCP func(net.Destination) (net.Conn, error)
DialUDP func(net.Destination) (net.Conn, error)
type Udpmask interface {
UDP()
WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error)
WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error)
}
type ListenConfig struct {
Listen func(net.Addr) (net.Listener, error)
ListenPacket func(net.Addr) (net.PacketConn, error)
type UdpmaskManager struct {
udpmasks []Udpmask
}
type TCPMask interface {
WrapConnClient(net.Conn, *net.Destination, *Dialer) (net.Conn, error)
WrapConnServer(net.Conn) (net.Conn, error)
// Listen(net.Listener) (net.Listener, error)
}
type UDPMask interface {
WrapPacketConnClient(net.PacketConn, *net.Destination, *Dialer) (net.PacketConn, error)
WrapPacketConnServer(net.PacketConn, net.Addr, *ListenConfig) (net.PacketConn, error)
}
type FinalMask struct {
tcpMasks []TCPMask
udpMasks []UDPMask
dialTCP func(context.Context, net.Destination) (net.Conn, error)
listen func(context.Context, net.Addr) (net.Listener, error)
dialUDP func(context.Context, net.Destination) (net.PacketConn, net.Addr, error)
listenPacket func(context.Context, net.Addr) (net.PacketConn, error)
}
func NewFinalMask(tcpMasks []TCPMask, udpMasks []UDPMask, dialTCP func(context.Context, net.Destination) (net.Conn, error), listen func(context.Context, net.Addr) (net.Listener, error), dialUDP func(context.Context, net.Destination) (net.PacketConn, net.Addr, error), listenPacket func(context.Context, net.Addr) (net.PacketConn, error)) *FinalMask {
slices.Reverse(tcpMasks)
slices.Reverse(udpMasks)
return &FinalMask{
tcpMasks: tcpMasks,
udpMasks: udpMasks,
dialTCP: dialTCP,
dialUDP: dialUDP,
listen: listen,
listenPacket: listenPacket,
func NewUdpmaskManager(udpmasks []Udpmask) *UdpmaskManager {
return &UdpmaskManager{
udpmasks: udpmasks,
}
}
func (fm *FinalMask) DialTCP(ctx context.Context, dest net.Destination) (net.Conn, error) {
if len(fm.tcpMasks) == 0 {
return fm.dialTCP(ctx, dest)
}
for i := range fm.tcpMasks {
if i > 0 {
if _, ok := fm.tcpMasks[i].(interface{ HandleDial() }); ok {
return nil, fmt.Errorf("incorrect index: %d %T", i, fm.tcpMasks[i])
}
}
}
var conn net.Conn
var err error
if _, ok := fm.tcpMasks[0].(interface{ HandleDial() }); !ok {
conn, err = fm.dialTCP(ctx, dest)
if err != nil {
return nil, err
}
}
dialer := &Dialer{
DialTCP: func(dest net.Destination) (net.Conn, error) {
return fm.dialTCP(ctx, dest)
},
DialUDP: func(dest net.Destination) (net.Conn, error) {
conn, addr, err := fm.dialUDP(ctx, dest)
if err != nil {
return nil, err
}
return &PacketConnWrapper{PacketConn: conn, udpAddr: addr}, err
},
}
for i := range fm.tcpMasks {
var newConn net.Conn
newConn, err = fm.tcpMasks[i].WrapConnClient(conn, &dest, dialer)
if err != nil {
_ = conn.Close()
return nil, err
}
conn = newConn
}
return conn, nil
}
func (fm *FinalMask) Listen(ctx context.Context, addr net.Addr) (net.Listener, error) {
if len(fm.tcpMasks) == 0 {
return fm.listen(ctx, addr)
}
off := 0
listener, err := fm.listen(ctx, addr)
if err != nil {
return nil, err
}
for i := range fm.tcpMasks {
if _, ok := fm.tcpMasks[i].(interface {
Listen(net.Listener) (net.Listener, error)
}); ok {
if i-off == 0 {
l, err := fm.tcpMasks[i].(interface {
Listen(net.Listener) (net.Listener, error)
}).Listen(listener)
if err != nil {
listener.Close()
return nil, err
}
listener = l
} else {
l, err := fm.tcpMasks[i].(interface {
Listen(net.Listener) (net.Listener, error)
}).Listen(&TCPListener{Listener: listener, tcpMasks: fm.tcpMasks[off:i]})
if err != nil {
listener.Close()
return nil, err
}
listener = l
}
off = i + 1
}
}
if off < len(fm.tcpMasks) {
return &TCPListener{Listener: listener, tcpMasks: fm.tcpMasks[off:]}, nil
}
return listener, nil
}
func (fm *FinalMask) DialUDP(ctx context.Context, dest net.Destination) (net.Conn, error) {
if len(fm.udpMasks) == 0 {
conn, addr, err := fm.dialUDP(ctx, dest)
if err != nil {
return nil, err
}
return &PacketConnWrapper{PacketConn: conn, udpAddr: addr}, nil
}
for i := range fm.udpMasks {
if i > 0 {
if _, ok := fm.udpMasks[i].(interface{ HandleDial() }); ok {
return nil, fmt.Errorf("incorrect index: %d %T", i, fm.udpMasks[i])
}
}
}
var conn net.PacketConn
var addr net.Addr
var err error
if _, ok := fm.udpMasks[0].(interface{ HandleDial() }); !ok {
conn, addr, err = fm.dialUDP(ctx, dest)
if err != nil {
return nil, err
}
}
dialer := &Dialer{
DialTCP: func(dest net.Destination) (net.Conn, error) {
return fm.dialTCP(ctx, dest)
},
DialUDP: func(dest net.Destination) (net.Conn, error) {
conn, addr, err := fm.dialUDP(ctx, dest)
if err != nil {
return nil, err
}
return &PacketConnWrapper{PacketConn: conn, udpAddr: addr}, err
},
}
func (m *UdpmaskManager) WrapPacketConnClient(raw net.PacketConn) (net.PacketConn, error) {
var sizes []int
var conns []net.PacketConn
for i := range fm.udpMasks {
var newConn net.PacketConn
if _, ok := fm.udpMasks[i].(interface{ HeaderConn() }); ok {
newConn, err = fm.udpMasks[i].WrapPacketConnClient(nil, nil, nil)
for i, mask := range slices.Backward(m.udpmasks) {
if _, ok := mask.(headerConn); ok {
conn, err := mask.WrapPacketConnClient(nil, i, len(m.udpmasks)-1)
if err != nil {
_ = conn.Close()
return nil, err
}
sizes = append(sizes, newConn.(interface{ Size() int }).Size())
conns = append(conns, newConn)
sizes = append(sizes, conn.(headerSize).Size())
conns = append(conns, conn)
} else {
if len(conns) > 0 {
conn = &headerManagerConn{PacketConn: conn, sizes: sizes, conns: conns}
raw = &headerManagerConn{sizes: sizes, conns: conns, PacketConn: raw}
sizes = nil
conns = nil
}
newConn, err = fm.udpMasks[i].WrapPacketConnClient(conn, &dest, dialer)
var err error
raw, err = mask.WrapPacketConnClient(raw, i, len(m.udpmasks)-1)
if err != nil {
_ = conn.Close()
return nil, err
}
conn = newConn
}
}
if len(conns) > 0 {
conn = &headerManagerConn{PacketConn: conn, sizes: sizes, conns: conns}
raw = &headerManagerConn{sizes: sizes, conns: conns, PacketConn: raw}
sizes = nil
conns = nil
}
if addr == nil {
addr = &net.UDPAddr{IP: []byte{0, 0, 0, 0}}
}
return &PacketConnWrapper{PacketConn: conn, udpAddr: addr}, nil
return raw, nil
}
func (fm *FinalMask) ListenPacket(ctx context.Context, addr net.Addr) (net.PacketConn, error) {
if len(fm.udpMasks) == 0 {
return fm.listenPacket(ctx, addr)
}
for i := range fm.udpMasks {
if i > 0 {
if _, ok := fm.udpMasks[i].(interface{ HandleListen() }); ok {
return nil, fmt.Errorf("incorrect index: %d %T", i, fm.udpMasks[i])
}
}
}
var conn net.PacketConn
var err error
if _, ok := fm.udpMasks[0].(interface{ HandleListen() }); !ok {
conn, err = fm.listenPacket(ctx, addr)
if err != nil {
return nil, err
}
}
lc := &ListenConfig{
Listen: func(addr net.Addr) (net.Listener, error) { return fm.listen(ctx, addr) },
ListenPacket: func(addr net.Addr) (net.PacketConn, error) { return fm.listenPacket(ctx, addr) },
}
func (m *UdpmaskManager) WrapPacketConnServer(raw net.PacketConn) (net.PacketConn, error) {
var sizes []int
var conns []net.PacketConn
for i := range fm.udpMasks {
var newConn net.PacketConn
if _, ok := fm.udpMasks[i].(interface{ HeaderConn() }); ok {
newConn, err = fm.udpMasks[i].WrapPacketConnServer(nil, nil, nil)
for i, mask := range slices.Backward(m.udpmasks) {
if _, ok := mask.(headerConn); ok {
conn, err := mask.WrapPacketConnServer(nil, i, len(m.udpmasks)-1)
if err != nil {
_ = conn.Close()
return nil, err
}
sizes = append(sizes, newConn.(interface{ Size() int }).Size())
conns = append(conns, newConn)
sizes = append(sizes, conn.(headerSize).Size())
conns = append(conns, conn)
} else {
if len(conns) > 0 {
conn = &headerManagerConn{PacketConn: conn, sizes: sizes, conns: conns}
raw = &headerManagerConn{sizes: sizes, conns: conns, PacketConn: raw}
sizes = nil
conns = nil
}
newConn, err = fm.udpMasks[i].WrapPacketConnServer(conn, addr, lc)
var err error
raw, err = mask.WrapPacketConnServer(raw, i, len(m.udpmasks)-1)
if err != nil {
_ = conn.Close()
return nil, err
}
conn = newConn
}
}
if len(conns) > 0 {
conn = &headerManagerConn{PacketConn: conn, sizes: sizes, conns: conns}
raw = &headerManagerConn{sizes: sizes, conns: conns, PacketConn: raw}
sizes = nil
conns = nil
}
return conn, nil
return raw, nil
}
const (
UDPSize = 4096
)
type PacketConnWrapper struct {
net.PacketConn
udpAddr net.Addr
type headerConn interface {
HeaderConn()
}
func (c *PacketConnWrapper) RemoteAddr() net.Addr {
return c.udpAddr
}
func (c *PacketConnWrapper) Read(b []byte) (n int, err error) {
n, _, err = c.PacketConn.ReadFrom(b)
return
}
func (c *PacketConnWrapper) Write(b []byte) (n int, err error) {
return c.PacketConn.WriteTo(b, c.udpAddr)
type headerSize interface {
Size() int
}
type headerManagerConn struct {
@@ -379,27 +194,75 @@ func (c *headerManagerConn) WriteTo(p []byte, addr net.Addr) (n int, err error)
return len(p), nil
}
type TCPListener struct {
net.Listener
tcpMasks []TCPMask
type Tcpmask interface {
TCP()
WrapConnClient(net.Conn) (net.Conn, error)
WrapConnServer(net.Conn) (net.Conn, error)
}
func (l *TCPListener) Accept() (net.Conn, error) {
type TcpmaskManager struct {
tcpmasks []Tcpmask
}
func NewTcpmaskManager(tcpmasks []Tcpmask) *TcpmaskManager {
return &TcpmaskManager{
tcpmasks: tcpmasks,
}
}
func (m *TcpmaskManager) WrapConnClient(raw net.Conn) (net.Conn, error) {
var err error
for _, mask := range slices.Backward(m.tcpmasks) {
raw, err = mask.WrapConnClient(raw)
if err != nil {
return nil, err
}
}
return raw, nil
}
func (m *TcpmaskManager) WrapConnServer(raw net.Conn) (net.Conn, error) {
var err error
for _, mask := range slices.Backward(m.tcpmasks) {
raw, err = mask.WrapConnServer(raw)
if err != nil {
return nil, err
}
}
return raw, nil
}
func (m *TcpmaskManager) WrapListener(l net.Listener) (net.Listener, error) {
return NewTcpListener(m, l)
}
type tcpListener struct {
m *TcpmaskManager
net.Listener
}
func NewTcpListener(m *TcpmaskManager, l net.Listener) (net.Listener, error) {
return &tcpListener{
m: m,
Listener: l,
}, nil
}
func (l *tcpListener) Accept() (net.Conn, error) {
conn, err := l.Listener.Accept()
if err != nil {
return conn, err
}
for i := range l.tcpMasks {
var newConn net.Conn
newConn, err = l.tcpMasks[i].WrapConnServer(conn)
if err != nil {
_ = conn.Close()
return nil, err
}
conn = newConn
newConn, err := l.m.WrapConnServer(conn)
if err != nil {
errors.LogDebugInner(context.Background(), err, "mask err")
_ = conn.Close()
return nil, err
}
return conn, nil
return newConn, nil
}
type TcpMaskConn interface {
@@ -1,14 +1,14 @@
package fragment
import (
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet/finalmask"
)
import "net"
func (c *Config) WrapConnClient(conn net.Conn, dest *net.Destination, dialer *finalmask.Dialer) (net.Conn, error) {
return NewConnClient(c, conn, false)
func (c *Config) TCP() {
}
func (c *Config) WrapConnServer(conn net.Conn) (net.Conn, error) {
return NewConnServer(c, conn, true)
func (c *Config) WrapConnClient(raw net.Conn) (net.Conn, error) {
return NewConnClient(c, raw, false)
}
func (c *Config) WrapConnServer(raw net.Conn) (net.Conn, error) {
return NewConnServer(c, raw, true)
}
@@ -1,30 +1,35 @@
package custom
import (
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet/finalmask"
"net"
)
func (c *TCPConfig) WrapConnClient(conn net.Conn, dest *net.Destination, dialer *finalmask.Dialer) (net.Conn, error) {
return NewConnClientTCP(c, conn)
func (c *TCPConfig) TCP() {}
func (c *TCPConfig) WrapConnClient(raw net.Conn) (net.Conn, error) {
return NewConnClientTCP(c, raw)
}
func (c *TCPConfig) WrapConnServer(conn net.Conn) (net.Conn, error) {
return NewConnServerTCP(c, conn)
func (c *TCPConfig) WrapConnServer(raw net.Conn) (net.Conn, error) {
return NewConnServerTCP(c, raw)
}
func (c *UDPConfig) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
return NewConnClientUDP(c, conn)
func (c *UDPConfig) UDP() {}
func (c *UDPConfig) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnClientUDP(c, raw)
}
func (c *UDPConfig) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
return NewConnServerUDP(c, conn)
func (c *UDPConfig) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnServerUDP(c, raw)
}
func (c *UDPStandaloneConfig) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
return NewConnClientUDPStandalone(c, conn)
func (c *UDPStandaloneConfig) UDP() {}
func (c *UDPStandaloneConfig) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnClientUDPStandalone(c, raw)
}
func (c *UDPStandaloneConfig) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
return NewConnServerUDPStandalone(c, conn)
func (c *UDPStandaloneConfig) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnServerUDPStandalone(c, raw)
}
@@ -9,6 +9,8 @@ import (
"strings"
"testing"
"time"
"github.com/xtls/xray-core/transport/internet/finalmask"
)
func TestMetadataEvaluatorRejectsUnknownName(t *testing.T) {
@@ -154,7 +156,7 @@ func TestMetadataUDPStandaloneWriteUsesRemotePort(t *testing.T) {
}
defer serverRaw.Close()
client, err := cfg.WrapPacketConnClient(clientRaw, nil, nil)
client, err := finalmask.NewUdpmaskManager([]finalmask.Udpmask{cfg}).WrapPacketConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
@@ -299,7 +301,7 @@ func TestMetadataTCPHandshakeUsesEndpointPorts(t *testing.T) {
}
defer serverRaw.Close()
client, err := clientCfg.WrapConnClient(clientRaw, nil, nil)
client, err := clientCfg.WrapConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
@@ -5,6 +5,8 @@ import (
"net"
"testing"
"time"
"github.com/xtls/xray-core/transport/internet/finalmask"
)
func mustSendRecvUDP(t *testing.T, from net.PacketConn, to net.PacketConn, msg []byte) {
@@ -46,6 +48,7 @@ func TestStateUDPResponseReusesPriorCapturedValues(t *testing.T) {
},
},
}
maskManager := finalmask.NewUdpmaskManager([]finalmask.Udpmask{cfg})
clientRaw, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
@@ -59,11 +62,11 @@ func TestStateUDPResponseReusesPriorCapturedValues(t *testing.T) {
}
defer serverRaw.Close()
client, err := cfg.WrapPacketConnClient(clientRaw, nil, nil)
client, err := maskManager.WrapPacketConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
server, err := cfg.WrapPacketConnServer(serverRaw, nil, nil)
server, err := maskManager.WrapPacketConnServer(serverRaw)
if err != nil {
t.Fatal(err)
}
@@ -37,7 +37,7 @@ func TestDSLTCPHandshakeReusesCapturedValue(t *testing.T) {
defer clientRaw.Close()
defer serverRaw.Close()
client, err := cfg.WrapConnClient(clientRaw, nil, nil)
client, err := cfg.WrapConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
@@ -117,7 +117,7 @@ func TestDSLTCPClientRejectsMismatchedResponseSequence(t *testing.T) {
defer clientRaw.Close()
defer serverRaw.Close()
client, err := clientCfg.WrapConnClient(clientRaw, nil, nil)
client, err := clientCfg.WrapConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
@@ -1,16 +1,17 @@
package aes128gcm
import (
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet/finalmask"
"net"
)
func (c *Config) UDP() {}
func (c *Config) HeaderConn() {}
func (c *Config) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
return NewConnClient(c, conn)
func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnClient(c, raw)
}
func (c *Config) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
return NewConnServer(c, conn)
func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnServer(c, raw)
}
@@ -1,16 +1,17 @@
package header
import (
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet/finalmask"
"net"
)
func (c *Config) UDP() {}
func (c *Config) HeaderConn() {}
func (c *Config) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
return NewConnClient(c, conn)
func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnClient(c, raw)
}
func (c *Config) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
return NewConnServer(c, conn)
func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnServer(c, raw)
}
@@ -1,16 +1,17 @@
package original
import (
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet/finalmask"
"net"
)
func (c *Config) UDP() {}
func (c *Config) HeaderConn() {}
func (c *Config) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
return NewConnClient(c, conn)
func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnClient(c, raw)
}
func (c *Config) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
return NewConnServer(c, conn)
func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnServer(c, raw)
}
+8 -8
View File
@@ -1,14 +1,14 @@
package noise
import (
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet/finalmask"
)
import "net"
func (c *Config) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
return NewConnClient(c, conn)
func (c *Config) UDP() {
}
func (c *Config) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
return NewConnServer(c, conn)
func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnClient(c, raw)
}
func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnServer(c, raw)
}
+19 -6
View File
@@ -1,14 +1,27 @@
package realm
import (
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet/finalmask"
"net"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/hysteria/udphop"
)
func (c *Config) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
return NewConnClient(c, conn)
func (c *Config) UDP() {}
func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
_, ok1 := raw.(*internet.FakePacketConn)
_, ok2 := raw.(*udphop.UdpHopPacketConn)
if level != 0 || ok1 || ok2 {
return nil, errors.New("realm requires being at the outermost level")
}
return NewConnClient(c, raw)
}
func (c *Config) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
return NewConnServer(c, conn)
func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
if level != 0 {
return nil, errors.New("realm requires being at the outermost level")
}
return NewConnServer(c, raw)
}
@@ -1,24 +1,27 @@
package salamander
import (
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet/finalmask"
"net"
)
func (c *Config) UDP() {}
func (c *Config) HeaderConn() {}
func (c *Config) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
return NewSalamanderConnClient(c, conn)
func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewSalamanderConnClient(c, raw)
}
func (c *Config) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
return NewSalamanderConnServer(c, conn)
func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewSalamanderConnServer(c, raw)
}
func (c *GeckoConfig) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
return NewGeckoConnClient(c, conn)
func (c *GeckoConfig) UDP() {}
func (c *GeckoConfig) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewGeckoConnClient(c, raw)
}
func (c *GeckoConfig) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
return NewGeckoConnServer(c, conn)
func (c *GeckoConfig) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewGeckoConnServer(c, raw)
}
+23 -10
View File
@@ -1,18 +1,25 @@
package sudoku
import (
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet/finalmask"
"net"
"github.com/xtls/xray-core/common/errors"
)
func (c *Config) TCP() {
}
func (c *Config) UDP() {
}
// Sudoku in finalmask mode is a pure appearance transform with no standalone handshake.
// TCP always keeps classic sudoku on uplink and uses packed downlink optimization on server writes.
func (c *Config) WrapConnClient(conn net.Conn, dest *net.Destination, dialer *finalmask.Dialer) (net.Conn, error) {
return newPackedDirectionalConn(conn, c, true)
func (c *Config) WrapConnClient(raw net.Conn) (net.Conn, error) {
return newPackedDirectionalConn(raw, c, true)
}
func (c *Config) WrapConnServer(conn net.Conn) (net.Conn, error) {
return newPackedDirectionalConn(conn, c, false)
func (c *Config) WrapConnServer(raw net.Conn) (net.Conn, error) {
return newPackedDirectionalConn(raw, c, false)
}
func newPackedDirectionalConn(raw net.Conn, config *Config, readPacked bool) (net.Conn, error) {
@@ -35,10 +42,16 @@ func newPackedDirectionalConn(raw net.Conn, config *Config, readPacked bool) (ne
return newWrappedConn(raw, reader, writer), nil
}
func (c *Config) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
return NewUDPConn(conn, c)
func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
if level != levelCount {
return nil, errors.New("sudoku udp mask must be the innermost mask in chain")
}
return NewUDPConn(raw, c)
}
func (c *Config) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
return NewUDPConn(conn, c)
func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
if level != levelCount {
return nil, errors.New("sudoku udp mask must be the innermost mask in chain")
}
return NewUDPConn(raw, c)
}
+55 -57
View File
@@ -2,14 +2,12 @@ package finalmask_test
import (
"bytes"
"context"
"io"
gonet "net"
"net"
"strings"
"testing"
"time"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet/finalmask"
"github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
)
@@ -22,14 +20,11 @@ func mustSendRecvTcp(
) {
t.Helper()
waitCh := make(chan error)
go func() {
_, err := from.Write(msg)
if err != nil {
t.Fatal(err)
t.Error(err)
}
close(waitCh)
}()
buf := make([]byte, 1024)
@@ -45,23 +40,18 @@ func mustSendRecvTcp(
if !bytes.Equal(buf[:n], msg) {
t.Fatalf("unexpected data %q", buf[:n])
}
<-waitCh
}
type layerMaskTcp struct {
name string
mask finalmask.TCPMask
mask finalmask.Tcpmask
}
type failingWrapMask struct{}
func (failingWrapMask) TCP() {}
func (f failingWrapMask) WrapConnClient(conn net.Conn, dest *net.Destination, dialer *finalmask.Dialer) (net.Conn, error) {
return conn, nil
}
func (f failingWrapMask) WrapConnServer(conn net.Conn) (net.Conn, error) {
func (failingWrapMask) TCP() {}
func (f failingWrapMask) WrapConnClient(raw net.Conn) (net.Conn, error) { return raw, nil }
func (f failingWrapMask) WrapConnServer(raw net.Conn) (net.Conn, error) {
return nil, io.ErrClosedPipe
}
@@ -102,31 +92,32 @@ func TestConnReadWrite(t *testing.T) {
t.Run(c.name, func(t *testing.T) {
mask := c.mask
dialTCP := func(ctx context.Context, dest net.Destination) (net.Conn, error) {
return net.Dial("tcp", dest.NetAddr())
}
listen := func(ctx context.Context, addr net.Addr) (net.Listener, error) {
return net.Listen("tcp", addr.String())
}
finalMask := finalmask.NewFinalMask([]finalmask.TCPMask{mask}, nil, dialTCP, listen, nil, nil)
maskManager := finalmask.NewTcpmaskManager([]finalmask.Tcpmask{mask})
listener, err := finalMask.Listen(context.Background(), &net.TCPAddr{IP: net.LocalHostIP.IP()})
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { listener.Close() })
client, err := finalMask.DialTCP(context.Background(), net.TCPDestination(net.IPAddress(listener.Addr().(*net.TCPAddr).IP), net.Port(listener.Addr().(*net.TCPAddr).Port)))
client, err := net.Dial("tcp", ln.Addr().String())
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { client.Close() })
server, err := listener.Accept()
client, err = maskManager.WrapConnClient(client)
if err != nil {
t.Fatal(err)
}
server, err := ln.Accept()
if err != nil {
t.Fatal(err)
}
server, err = maskManager.WrapConnServer(server)
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { server.Close() })
_ = client.SetDeadline(time.Now().Add(time.Second))
_ = server.SetDeadline(time.Now().Add(time.Second))
@@ -159,32 +150,34 @@ func TestTCPcustomStaticHandshakeRoundTrip(t *testing.T) {
},
},
}
maskManager := finalmask.NewTcpmaskManager([]finalmask.Tcpmask{cfg})
dialTCP := func(ctx context.Context, dest net.Destination) (net.Conn, error) {
return net.Dial("tcp", dest.NetAddr())
}
listen := func(ctx context.Context, addr net.Addr) (net.Listener, error) {
return net.Listen("tcp", addr.String())
}
finalMask := finalmask.NewFinalMask([]finalmask.TCPMask{cfg}, nil, dialTCP, listen, nil, nil)
listener, err := finalMask.Listen(context.Background(), &net.TCPAddr{IP: net.LocalHostIP.IP()})
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer listener.Close()
defer ln.Close()
client, err := finalMask.DialTCP(context.Background(), net.TCPDestination(net.IPAddress(listener.Addr().(*net.TCPAddr).IP), net.Port(listener.Addr().(*net.TCPAddr).Port)))
clientRaw, err := net.Dial("tcp", ln.Addr().String())
if err != nil {
t.Fatal(err)
}
defer client.Close()
defer clientRaw.Close()
server, err := listener.Accept()
serverRaw, err := ln.Accept()
if err != nil {
t.Fatal(err)
}
defer serverRaw.Close()
client, err := maskManager.WrapConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
server, err := maskManager.WrapConnServer(serverRaw)
if err != nil {
t.Fatal(err)
}
defer server.Close()
_ = client.SetDeadline(time.Now().Add(time.Second))
_ = server.SetDeadline(time.Now().Add(time.Second))
@@ -227,11 +220,11 @@ func TestTCPcustomClientRejectsMismatchedServerSequence(t *testing.T) {
},
}
clientRaw, serverRaw := gonet.Pipe()
clientRaw, serverRaw := net.Pipe()
defer clientRaw.Close()
defer serverRaw.Close()
client, err := clientCfg.WrapConnClient(clientRaw, nil, nil)
client, err := clientCfg.WrapConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
@@ -264,37 +257,42 @@ func TestTCPcustomClientRejectsMismatchedServerSequence(t *testing.T) {
}
func TestTCPWrapListenerRejectsImmediateWrapErrors(t *testing.T) {
dialTCP := func(ctx context.Context, dest net.Destination) (net.Conn, error) {
return net.Dial("tcp", dest.NetAddr())
}
listen := func(ctx context.Context, addr net.Addr) (net.Listener, error) {
return net.Listen("tcp", addr.String())
}
finalMask := finalmask.NewFinalMask([]finalmask.TCPMask{failingWrapMask{}}, nil, dialTCP, listen, nil, nil)
clientManager := finalmask.NewTcpmaskManager([]finalmask.Tcpmask{failingWrapMask{}})
serverManager := finalmask.NewTcpmaskManager([]finalmask.Tcpmask{failingWrapMask{}})
listener, err := finalMask.Listen(context.Background(), &net.TCPAddr{IP: net.LocalHostIP.IP()})
rawLn, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer rawLn.Close()
ln, err := serverManager.WrapListener(rawLn)
if err != nil {
t.Fatal(err)
}
defer listener.Close()
accepted := make(chan struct {
conn net.Conn
err error
}, 1)
go func() {
conn, err := listener.Accept()
conn, err := ln.Accept()
accepted <- struct {
conn net.Conn
err error
}{conn: conn, err: err}
}()
client, err := finalMask.DialTCP(context.Background(), net.TCPDestination(net.IPAddress(listener.Addr().(*net.TCPAddr).IP), net.Port(listener.Addr().(*net.TCPAddr).Port)))
clientRaw, err := net.Dial("tcp", rawLn.Addr().String())
if err != nil {
t.Fatal(err)
}
defer clientRaw.Close()
client, err := clientManager.WrapConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
defer client.Close()
_ = client.SetDeadline(time.Now().Add(time.Second))
+59 -52
View File
@@ -2,15 +2,13 @@ package finalmask_test
import (
"bytes"
"context"
"encoding/binary"
"io"
gonet "net"
"net"
"sync/atomic"
"testing"
"time"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/proxy"
"github.com/xtls/xray-core/transport/internet/finalmask"
"github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
@@ -53,7 +51,7 @@ func mustSendRecv(
type layerMask struct {
name string
mask finalmask.UDPMask
mask finalmask.Udpmask
layers int
}
@@ -215,23 +213,25 @@ func newStandaloneStunLikeUDPServerConfig() *custom.UDPStandaloneConfig {
func newUDPClientServerPair(t *testing.T, cfg *custom.UDPStandaloneConfig) (net.PacketConn, net.PacketConn, net.PacketConn, net.PacketConn) {
t.Helper()
clientRaw, err := gonet.ListenPacket("udp", "127.0.0.1:0")
clientRaw, err := net.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")
serverRaw, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = serverRaw.Close() })
client, err := cfg.WrapPacketConnClient(clientRaw, nil, nil)
maskManager := finalmask.NewUdpmaskManager([]finalmask.Udpmask{cfg})
client, err := maskManager.WrapPacketConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
server, err := cfg.WrapPacketConnServer(serverRaw, nil, nil)
server, err := maskManager.WrapPacketConnServer(serverRaw)
if err != nil {
t.Fatal(err)
}
@@ -348,39 +348,31 @@ func TestPacketConnReadWrite(t *testing.T) {
if layers <= 0 {
layers = 1
}
masks := make([]finalmask.UDPMask, 0, layers)
masks := make([]finalmask.Udpmask, 0, layers)
for i := 0; i < layers; i++ {
masks = append(masks, mask)
}
maskManager := finalmask.NewUdpmaskManager(masks)
dialUDP := func(ctx context.Context, dest net.Destination) (net.PacketConn, net.Addr, error) {
udpAddr, err := net.ResolveUDPAddr("udp", dest.NetAddr())
if err != nil {
return nil, nil, err
}
conn, err := gonet.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
return nil, nil, err
}
return conn, udpAddr, nil
}
listenPacket := func(ctx context.Context, addr net.Addr) (net.PacketConn, error) {
return gonet.ListenPacket(addr.Network(), addr.String())
}
finalMask := finalmask.NewFinalMask(nil, masks, nil, nil, dialUDP, listenPacket)
server, err := finalMask.ListenPacket(context.Background(), &net.UDPAddr{IP: net.LocalHostIP.IP()})
client, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { server.Close() })
clientConn, err := finalMask.DialUDP(context.Background(), net.UDPDestination(net.IPAddress(server.LocalAddr().(*net.UDPAddr).IP), net.Port(server.LocalAddr().(*net.UDPAddr).Port)))
client, err = maskManager.WrapPacketConnClient(client)
if err != nil {
t.Fatal(err)
}
server, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
server, err = maskManager.WrapPacketConnServer(server)
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { clientConn.Close() })
client := clientConn.(*finalmask.PacketConnWrapper).PacketConn
_ = client.SetDeadline(time.Now().Add(time.Second))
_ = server.SetDeadline(time.Now().Add(time.Second))
@@ -405,20 +397,21 @@ func TestUDPcustomStaticHeaderWireShape(t *testing.T) {
{Rand: 1, RandMin: 0x30, RandMax: 0x40},
},
}
maskManager := finalmask.NewUdpmaskManager([]finalmask.Udpmask{cfg})
clientRaw, err := gonet.ListenPacket("udp", "127.0.0.1:0")
clientRaw, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer clientRaw.Close()
serverRaw, err := gonet.ListenPacket("udp", "127.0.0.1:0")
serverRaw, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer serverRaw.Close()
client, err := cfg.WrapPacketConnClient(clientRaw, nil, nil)
client, err := maskManager.WrapPacketConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
@@ -649,11 +642,11 @@ func TestSudokuBDD(t *testing.T) {
Ascii: "prefer_ascii",
}
clientRaw, serverRaw := gonet.Pipe()
clientRaw, serverRaw := net.Pipe()
defer clientRaw.Close()
defer serverRaw.Close()
clientConn, err := cfg.WrapConnClient(clientRaw, nil, nil)
clientConn, err := cfg.WrapConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
@@ -690,11 +683,11 @@ func TestSudokuBDD(t *testing.T) {
PaddingMax: 0,
}
clientRaw, serverRaw := gonet.Pipe()
clientRaw, serverRaw := net.Pipe()
defer clientRaw.Close()
defer serverRaw.Close()
clientConn, err := cfg.WrapConnClient(clientRaw, nil, nil)
clientConn, err := cfg.WrapConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
@@ -745,10 +738,10 @@ func TestSudokuBDD(t *testing.T) {
countWireBytes := func(wrapServer func(net.Conn, *sudoku.Config) (net.Conn, error), cfg *sudoku.Config) int64 {
t.Helper()
clientRaw, serverRaw := gonet.Pipe()
clientRaw, serverRaw := net.Pipe()
watchedServerRaw := &countingConn{Conn: serverRaw}
clientConn, err := cfg.WrapConnClient(clientRaw, nil, nil)
clientConn, err := cfg.WrapConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
@@ -800,11 +793,11 @@ func TestSudokuBDD(t *testing.T) {
CustomTables: []string{"xpxvvpvv", "vxpvxvvp"},
}
clientRaw, serverRaw := gonet.Pipe()
clientRaw, serverRaw := net.Pipe()
defer clientRaw.Close()
defer serverRaw.Close()
clientConn, err := cfg.WrapConnClient(clientRaw, nil, nil)
clientConn, err := cfg.WrapConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
@@ -842,11 +835,11 @@ func TestSudokuBDD(t *testing.T) {
PaddingMax: 0,
}
clientRaw, serverRaw := gonet.Pipe()
clientRaw, serverRaw := net.Pipe()
defer clientRaw.Close()
defer serverRaw.Close()
clientConn, err := cfg.WrapConnClient(clientRaw, nil, nil)
clientConn, err := cfg.WrapConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
@@ -875,6 +868,19 @@ func TestSudokuBDD(t *testing.T) {
}
})
t.Run("GivenSudokuUDPMask_WhenNotInnermost_ThenWrapFails", func(t *testing.T) {
cfg := &sudoku.Config{Password: "sudoku-udp"}
raw, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer raw.Close()
if _, err := cfg.WrapPacketConnClient(raw, 0, 1); err == nil {
t.Fatal("expected innermost check failure")
}
})
t.Run("GivenSudokuMultiTableUDPMask_WhenClientSendsMultipleDatagrams_ThenPayloadMatches", func(t *testing.T) {
cfg := &sudoku.Config{
Password: "sudoku-udp-multi",
@@ -883,24 +889,25 @@ func TestSudokuBDD(t *testing.T) {
PaddingMin: 0,
PaddingMax: 0,
}
maskManager := finalmask.NewUdpmaskManager([]finalmask.Udpmask{cfg})
clientRaw, err := gonet.ListenPacket("udp", "127.0.0.1:0")
clientRaw, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer clientRaw.Close()
serverRaw, err := gonet.ListenPacket("udp", "127.0.0.1:0")
serverRaw, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer serverRaw.Close()
client, err := cfg.WrapPacketConnClient(clientRaw, nil, nil)
client, err := maskManager.WrapPacketConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
server, err := cfg.WrapPacketConnServer(serverRaw, nil, nil)
server, err := maskManager.WrapPacketConnServer(serverRaw)
if err != nil {
t.Fatal(err)
}
@@ -954,7 +961,7 @@ func TestSudokuBDD(t *testing.T) {
}
defer serverRaw.Close()
clientConn, err := cfg.WrapConnClient(clientRaw, nil, nil)
clientConn, err := cfg.WrapConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
@@ -1001,11 +1008,11 @@ func TestSudokuBDD(t *testing.T) {
Ascii: "prefer_entropy",
}
clientRaw, serverRaw := gonet.Pipe()
clientRaw, serverRaw := net.Pipe()
defer clientRaw.Close()
defer serverRaw.Close()
clientConn, err := cfg.WrapConnClient(clientRaw, nil, nil)
clientConn, err := cfg.WrapConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
@@ -1025,11 +1032,11 @@ func TestSudokuBDD(t *testing.T) {
Ascii: "prefer_entropy",
}
clientRaw, serverRaw := gonet.Pipe()
clientRaw, serverRaw := net.Pipe()
defer clientRaw.Close()
defer serverRaw.Close()
clientConn, err := cfg.WrapConnClient(clientRaw, nil, nil)
clientConn, err := cfg.WrapConnClient(clientRaw)
if err != nil {
t.Fatal(err)
}
@@ -1,17 +0,0 @@
package udphop
import (
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet/finalmask"
)
func (c *Config) HandleDial() {}
func (c *Config) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
return NewUDPHopConn(c, dest, dialer)
}
func (c *Config) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
return nil, errors.New("udphop: client only")
}
@@ -1,178 +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/udphop/config.proto
package udphop
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type Config struct {
state protoimpl.MessageState `protogen:"open.v1"`
Local bool `protobuf:"varint,2,opt,name=local,proto3" json:"local,omitempty"`
Remote bool `protobuf:"varint,3,opt,name=remote,proto3" json:"remote,omitempty"`
RemoteOnce bool `protobuf:"varint,4,opt,name=remote_once,json=remoteOnce,proto3" json:"remote_once,omitempty"`
IntervalMin int64 `protobuf:"varint,5,opt,name=interval_min,json=intervalMin,proto3" json:"interval_min,omitempty"`
IntervalMax int64 `protobuf:"varint,6,opt,name=interval_max,json=intervalMax,proto3" json:"interval_max,omitempty"`
RemoteIPs []string `protobuf:"bytes,7,rep,name=remoteIPs,proto3" json:"remoteIPs,omitempty"`
RemotePorts []uint32 `protobuf:"varint,8,rep,packed,name=remote_ports,json=remotePorts,proto3" json:"remote_ports,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *Config) Reset() {
*x = Config{}
mi := &file_transport_internet_finalmask_udphop_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_udphop_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_udphop_config_proto_rawDescGZIP(), []int{0}
}
func (x *Config) GetLocal() bool {
if x != nil {
return x.Local
}
return false
}
func (x *Config) GetRemote() bool {
if x != nil {
return x.Remote
}
return false
}
func (x *Config) GetRemoteOnce() bool {
if x != nil {
return x.RemoteOnce
}
return false
}
func (x *Config) GetIntervalMin() int64 {
if x != nil {
return x.IntervalMin
}
return 0
}
func (x *Config) GetIntervalMax() int64 {
if x != nil {
return x.IntervalMax
}
return 0
}
func (x *Config) GetRemoteIPs() []string {
if x != nil {
return x.RemoteIPs
}
return nil
}
func (x *Config) GetRemotePorts() []uint32 {
if x != nil {
return x.RemotePorts
}
return nil
}
var File_transport_internet_finalmask_udphop_config_proto protoreflect.FileDescriptor
const file_transport_internet_finalmask_udphop_config_proto_rawDesc = "" +
"\n" +
"0transport/internet/finalmask/udphop/config.proto\x12(xray.transport.internet.finalmask.udphop\"\xe4\x01\n" +
"\x06Config\x12\x14\n" +
"\x05local\x18\x02 \x01(\bR\x05local\x12\x16\n" +
"\x06remote\x18\x03 \x01(\bR\x06remote\x12\x1f\n" +
"\vremote_once\x18\x04 \x01(\bR\n" +
"remoteOnce\x12!\n" +
"\finterval_min\x18\x05 \x01(\x03R\vintervalMin\x12!\n" +
"\finterval_max\x18\x06 \x01(\x03R\vintervalMax\x12\x1c\n" +
"\tremoteIPs\x18\a \x03(\tR\tremoteIPs\x12!\n" +
"\fremote_ports\x18\b \x03(\rR\vremotePortsJ\x04\b\x01\x10\x02B\x9a\x01\n" +
",com.xray.transport.internet.finalmask.udphopP\x01Z=github.com/xtls/xray-core/transport/internet/finalmask/udphop\xaa\x02(Xray.Transport.Internet.Finalmask.Udphopb\x06proto3"
var (
file_transport_internet_finalmask_udphop_config_proto_rawDescOnce sync.Once
file_transport_internet_finalmask_udphop_config_proto_rawDescData []byte
)
func file_transport_internet_finalmask_udphop_config_proto_rawDescGZIP() []byte {
file_transport_internet_finalmask_udphop_config_proto_rawDescOnce.Do(func() {
file_transport_internet_finalmask_udphop_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_udphop_config_proto_rawDesc), len(file_transport_internet_finalmask_udphop_config_proto_rawDesc)))
})
return file_transport_internet_finalmask_udphop_config_proto_rawDescData
}
var file_transport_internet_finalmask_udphop_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_transport_internet_finalmask_udphop_config_proto_goTypes = []any{
(*Config)(nil), // 0: xray.transport.internet.finalmask.udphop.Config
}
var file_transport_internet_finalmask_udphop_config_proto_depIdxs = []int32{
0, // [0:0] is the sub-list for method output_type
0, // [0:0] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_transport_internet_finalmask_udphop_config_proto_init() }
func file_transport_internet_finalmask_udphop_config_proto_init() {
if File_transport_internet_finalmask_udphop_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_udphop_config_proto_rawDesc), len(file_transport_internet_finalmask_udphop_config_proto_rawDesc)),
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_transport_internet_finalmask_udphop_config_proto_goTypes,
DependencyIndexes: file_transport_internet_finalmask_udphop_config_proto_depIdxs,
MessageInfos: file_transport_internet_finalmask_udphop_config_proto_msgTypes,
}.Build()
File_transport_internet_finalmask_udphop_config_proto = out.File
file_transport_internet_finalmask_udphop_config_proto_goTypes = nil
file_transport_internet_finalmask_udphop_config_proto_depIdxs = nil
}
@@ -1,19 +0,0 @@
syntax = "proto3";
package xray.transport.internet.finalmask.udphop;
option csharp_namespace = "Xray.Transport.Internet.Finalmask.Udphop";
option go_package = "github.com/xtls/xray-core/transport/internet/finalmask/udphop";
option java_package = "com.xray.transport.internet.finalmask.udphop";
option java_multiple_files = true;
message Config {
reserved 1;
bool local = 2;
bool remote = 3;
bool remote_once = 4;
int64 interval_min = 5;
int64 interval_max = 6;
repeated string remoteIPs = 7;
repeated uint32 remote_ports = 8;
}
-289
View File
@@ -1,289 +0,0 @@
package udphop
import (
"context"
"crypto/rand"
goerrors "errors"
"io"
mrand "math/rand"
"net/netip"
"sync"
"time"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/crypto"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet/finalmask"
)
var pool = sync.Pool{
New: func() any {
return make([]byte, finalmask.UDPSize)
},
}
type packet struct {
p []byte
addr net.Addr
err error
}
type udpHopConn struct {
dialer *finalmask.Dialer
local bool
remote bool
intervalMin int64
intervalMax int64
remoteIPs []netip.Prefix
remotePorts []uint32
deadline time.Time
readDeadline time.Time
writeDeadline time.Time
pre net.PacketConn
cur net.PacketConn
addr *net.UDPAddr
readCh chan packet
closeCh chan struct{}
wg sync.WaitGroup
mu sync.RWMutex
}
func NewUDPHopConn(c *Config, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
if c.IntervalMin < 5 || c.IntervalMax < 5 {
return nil, errors.New("invalid interval")
}
remoteIPs := make([]netip.Prefix, 0, len(c.RemoteIPs))
for _, ip := range c.RemoteIPs {
remoteIPs = append(remoteIPs, netip.MustParsePrefix(ip))
}
remotePorts := c.RemotePorts
if c.Remote || c.RemoteOnce {
if len(remoteIPs) > 0 {
dest.Address = net.IPAddress(randPrefix(remoteIPs[mrand.Intn(len(remoteIPs))]))
}
if len(remotePorts) > 0 {
dest.Port = net.Port(remotePorts[mrand.Intn(len(remotePorts))])
}
}
conn, err := dialer.DialUDP(*dest)
if err != nil {
return nil, err
}
cur := conn.(*finalmask.PacketConnWrapper).PacketConn
addr := conn.RemoteAddr().(*net.UDPAddr)
client := &udpHopConn{
dialer: dialer,
local: c.Local,
remote: c.Remote,
intervalMin: c.IntervalMin,
intervalMax: c.IntervalMax,
remoteIPs: remoteIPs,
remotePorts: remotePorts,
cur: cur,
addr: addr,
readCh: make(chan packet),
closeCh: make(chan struct{}),
}
go client.run()
client.wg.Add(1)
go client.recv(client.cur)
return client, nil
}
func (c *udpHopConn) closed() bool {
select {
case <-c.closeCh:
return true
default:
return false
}
}
func (c *udpHopConn) run() {
ticker := time.NewTicker(time.Second * time.Duration(crypto.RandBetween(c.intervalMin, c.intervalMax+1)))
defer ticker.Stop()
for {
select {
case <-c.closeCh:
return
case <-ticker.C:
ticker.Reset(time.Second * time.Duration(crypto.RandBetween(c.intervalMin, c.intervalMax+1)))
c.hop()
}
}
}
func (c *udpHopConn) hop() {
c.mu.Lock()
defer c.mu.Unlock()
if c.closed() {
return
}
oldIP := c.addr.IP
oldPort := c.addr.Port
if c.remote {
if len(c.remoteIPs) > 0 {
c.addr.IP = randPrefix(c.remoteIPs[mrand.Intn(len(c.remoteIPs))])
}
if len(c.remotePorts) > 0 {
c.addr.Port = int(c.remotePorts[mrand.Intn(len(c.remotePorts))])
}
}
if c.local {
conn, err := c.dialer.DialUDP(net.UDPDestination(net.IPAddress(c.addr.IP), net.Port(c.addr.Port)))
if err != nil {
c.addr.IP = oldIP
c.addr.Port = oldPort
errors.LogErrorInner(context.Background(), err, "hop err")
return
}
conn.SetDeadline(c.deadline)
conn.SetReadDeadline(c.readDeadline)
conn.SetWriteDeadline(c.writeDeadline)
if c.pre != nil {
_ = c.pre.Close()
}
c.pre = c.cur
c.cur = conn.(*finalmask.PacketConnWrapper).PacketConn
c.wg.Add(1)
go c.recv(c.cur)
}
}
func (c *udpHopConn) recv(conn net.PacketConn) {
defer c.wg.Done()
for {
p := pool.Get().([]byte)
n, addr, err := conn.ReadFrom(p)
if err != nil {
pool.Put(p[:cap(p)])
if c.closed() {
return
}
var netErr net.Error
if goerrors.As(err, &netErr) && netErr.Timeout() {
select {
case c.readCh <- packet{err: err}:
case <-c.closeCh:
return
}
continue
}
errors.LogErrorInner(context.Background(), err, "recv err")
return
}
select {
case c.readCh <- packet{p: p[:n], addr: addr}:
case <-c.closeCh:
pool.Put(p[:cap(p)])
return
}
}
}
func (c *udpHopConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
packet, ok := <-c.readCh
if ok {
if packet.p != nil {
n = copy(p, packet.p)
pool.Put(packet.p[:cap(packet.p)])
}
return n, packet.addr, packet.err
}
return 0, nil, io.ErrClosedPipe
}
func (c *udpHopConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
c.mu.RLock()
defer c.mu.RUnlock()
_, err = c.cur.WriteTo(p, c.addr)
if err != nil {
errors.LogErrorInner(context.Background(), err, "send err")
return 0, err
}
return len(p), nil
}
func (c *udpHopConn) Close() error {
c.mu.Lock()
defer c.mu.Unlock()
if c.closed() {
return nil
}
close(c.closeCh)
if c.pre != nil {
_ = c.pre.Close()
}
_ = c.cur.Close()
c.wg.Wait()
select {
case packet := <-c.readCh:
if packet.p != nil {
pool.Put(packet.p[:cap(packet.p)])
}
default:
}
close(c.readCh)
return nil
}
func (c *udpHopConn) LocalAddr() net.Addr {
c.mu.RLock()
defer c.mu.RUnlock()
return c.cur.LocalAddr()
}
func (c *udpHopConn) SetDeadline(t time.Time) error {
c.mu.Lock()
defer c.mu.Unlock()
c.deadline = t
if c.pre != nil {
_ = c.pre.SetDeadline(t)
}
return c.cur.SetDeadline(t)
}
func (c *udpHopConn) SetReadDeadline(t time.Time) error {
c.mu.Lock()
defer c.mu.Unlock()
c.readDeadline = t
if c.pre != nil {
_ = c.pre.SetReadDeadline(t)
}
return c.cur.SetReadDeadline(t)
}
func (c *udpHopConn) SetWriteDeadline(t time.Time) error {
c.mu.Lock()
defer c.mu.Unlock()
c.writeDeadline = t
if c.pre != nil {
_ = c.pre.SetWriteDeadline(t)
}
return c.cur.SetWriteDeadline(t)
}
func randPrefix(p netip.Prefix) []byte {
if p.IsSingleIP() {
return p.Addr().AsSlice()
}
b := p.Addr().AsSlice()
prefix := p.Bits()
var new [16]byte
common.Must2(rand.Read(new[:len(b)]))
i := prefix / 8
j := prefix % 8
if i+1 < len(b) {
copy(b[i+1:], new[i+1:])
}
mask := byte(0xff << (8 - j))
b[i] = (b[i] & mask) | (new[i] &^ mask)
return b
}
+16 -6
View File
@@ -1,14 +1,24 @@
package xdns
import (
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet/finalmask"
"net"
)
func (c *Config) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
return NewConnClient(c, conn)
func (c *Config) UDP() {
}
func (c *Config) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
return NewConnServer(c, conn)
func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
// _, ok1 := raw.(*internet.FakePacketConn)
// _, ok2 := raw.(*udphop.UdpHopPacketConn)
// if level != 0 || ok1 || ok2 {
// return nil, errors.New("xdns requires being at the outermost level")
// }
return NewConnClient(c, raw)
}
func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
// if level != 0 {
// return nil, errors.New("xdns requires being at the outermost level")
// }
return NewConnServer(c, raw)
}
+48 -55
View File
@@ -8,7 +8,8 @@ import (
goerrors "errors"
"fmt"
"io"
mrand "math/rand"
mathrand "math/rand"
"net"
"net/netip"
"sync"
"time"
@@ -16,7 +17,6 @@ import (
"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/finalmask"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
@@ -36,21 +36,20 @@ type packet struct {
}
type xicmpConnClient struct {
conn net.PacketConn
icmp4 *icmp.PacketConn
icmp6 *icmp.PacketConn
udp bool
ips []netip.Addr
ip net.IP
clientID [8]byte
id int
seq int
readCh chan packet
closeCh chan struct{}
wg sync.WaitGroup
closedCh chan struct{}
mu sync.Mutex
}
func NewConnClient(c *Config, dest *net.Destination) (net.PacketConn, error) {
func NewConnClient(c *Config, raw net.PacketConn) (net.PacketConn, error) {
var icmp4, icmp6 *icmp.PacketConn
var err4, err6 error
if c.DGRAM {
@@ -69,39 +68,35 @@ func NewConnClient(c *Config, dest *net.Destination) (net.PacketConn, error) {
ips = append(ips, netip.MustParseAddr(ip))
}
var ip net.IP
if len(ips) > 0 {
ip = ips[mrand.Intn(len(ips))].AsSlice()
} else {
ip = dest.Address.IP()
}
var clientID [8]byte
common.Must2(rand.Read(clientID[:]))
conn := &xicmpConnClient{
conn: raw,
icmp4: icmp4,
icmp6: icmp6,
udp: c.DGRAM,
ips: ips,
ip: ip,
clientID: clientID,
id: mrand.Intn(65536),
id: mathrand.Intn(65536),
seq: 1,
readCh: make(chan packet),
closeCh: make(chan struct{}),
closedCh: make(chan struct{}),
}
conn.wg.Add(2)
go conn.recv4()
go conn.recv6()
return conn, nil
}
func (c *xicmpConnClient) ring(a, b uint16) uint16 {
return min(a-b, b-a)
}
func (c *xicmpConnClient) closed() bool {
select {
case <-c.closeCh:
case <-c.closedCh:
return true
default:
return false
@@ -109,28 +104,26 @@ func (c *xicmpConnClient) closed() bool {
}
func (c *xicmpConnClient) recv4() {
defer c.wg.Done()
var b [finalmask.UDPSize]byte
for {
if c.closed() {
return
}
n, addr, err := c.icmp4.ReadFrom(b[:])
if err != nil {
if c.closed() {
return
}
var netErr net.Error
if goerrors.As(err, &netErr) && netErr.Timeout() {
select {
case c.readCh <- packet{
err: err,
}:
case <-c.closeCh:
case <-c.closedCh:
return
}
continue
}
errors.LogErrorInner(context.Background(), err, "recv err 4")
return
continue
}
msg, err := icmp.ParseMessage(1, b[:n])
@@ -153,6 +146,10 @@ func (c *xicmpConnClient) recv4() {
continue
}
if c.ring(uint16(echo.Seq), uint16(c.seq)) > 1000 {
continue
}
if len(echo.Data) > 8 && bytes.Equal(echo.Data[:8], c.clientID[:]) {
continue
}
@@ -169,7 +166,7 @@ func (c *xicmpConnClient) recv4() {
p: p,
addr: addr,
}:
case <-c.closeCh:
case <-c.closedCh:
pool.Put(p)
return
}
@@ -177,28 +174,26 @@ func (c *xicmpConnClient) recv4() {
}
func (c *xicmpConnClient) recv6() {
defer c.wg.Done()
var b [finalmask.UDPSize]byte
for {
if c.closed() {
break
}
n, addr, err := c.icmp6.ReadFrom(b[:])
if err != nil {
if c.closed() {
return
}
var netErr net.Error
if goerrors.As(err, &netErr) && netErr.Timeout() {
select {
case c.readCh <- packet{
err: err,
}:
case <-c.closeCh:
case <-c.closedCh:
return
}
continue
}
errors.LogErrorInner(context.Background(), err, "recv err 6")
return
continue
}
msg, err := icmp.ParseMessage(58, b[:n])
@@ -221,6 +216,10 @@ func (c *xicmpConnClient) recv6() {
continue
}
if c.ring(uint16(echo.Seq), uint16(c.seq)) > 1000 {
continue
}
if len(echo.Data) > 8 && bytes.Equal(echo.Data[:8], c.clientID[:]) {
continue
}
@@ -237,7 +236,7 @@ func (c *xicmpConnClient) recv6() {
p: p,
addr: addr,
}:
case <-c.closeCh:
case <-c.closedCh:
pool.Put(p)
return
}
@@ -245,15 +244,16 @@ func (c *xicmpConnClient) recv6() {
}
func (c *xicmpConnClient) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
packet, ok := <-c.readCh
if ok {
select {
case packet := <-c.readCh:
if packet.p != nil {
n = copy(p, packet.p)
pool.Put(packet.p)
}
return n, packet.addr, packet.err
case <-c.closedCh:
return 0, nil, io.EOF
}
return 0, nil, io.EOF
}
func (c *xicmpConnClient) WriteTo(p []byte, addr net.Addr) (n int, err error) {
@@ -268,9 +268,9 @@ func (c *xicmpConnClient) WriteTo(p []byte, addr net.Addr) (n int, err error) {
c.seq %= 65536
c.mu.Unlock()
ip := c.ip
ip := addr.(*net.UDPAddr).IP
if len(c.ips) > 0 {
ip = c.ips[mrand.Intn(len(c.ips))].AsSlice()
ip = c.ips[mathrand.Intn(len(c.ips))].AsSlice()
}
if c.udp {
@@ -294,9 +294,10 @@ func (c *xicmpConnClient) WriteTo(p []byte, addr net.Addr) (n int, err error) {
}
if err != nil {
errors.LogErrorInner(context.Background(), err, "send err")
errors.LogErrorInner(context.Background(), err, "xicmp write")
return 0, err
}
return len(p), nil
}
@@ -306,23 +307,15 @@ func (c *xicmpConnClient) Close() error {
if c.closed() {
return nil
}
close(c.closeCh)
close(c.closedCh)
_ = c.icmp4.Close()
_ = c.icmp6.Close()
c.wg.Wait()
select {
case p := <-c.readCh:
if p.p != nil {
pool.Put(p.p)
}
default:
}
close(c.readCh)
_ = c.conn.Close()
return nil
}
func (c *xicmpConnClient) LocalAddr() net.Addr {
return &net.UDPAddr{IP: []byte{0, 0, 0, 0}}
return c.conn.LocalAddr()
}
func (c *xicmpConnClient) SetDeadline(t time.Time) error {
+17 -12
View File
@@ -1,23 +1,28 @@
package xicmp
import (
"errors"
"net"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet/finalmask"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/hysteria/udphop"
)
func (c *Config) HandleDial() {}
func (c *Config) UDP() {
}
func (c *Config) HandleListen() {}
func (c *Config) WrapPacketConnClient(conn net.PacketConn, dest *net.Destination, dialer *finalmask.Dialer) (net.PacketConn, error) {
if dest.Address.Family().IsDomain() && len(c.IPs) == 0 {
return nil, errors.New("empty ip addresses")
func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
_, ok1 := raw.(*internet.FakePacketConn)
_, ok2 := raw.(*udphop.UdpHopPacketConn)
if level != 0 || ok1 || ok2 {
return nil, errors.New("xicmp requires being at the outermost level")
}
return NewConnClient(c, dest)
return NewConnClient(c, raw)
}
func (c *Config) WrapPacketConnServer(conn net.PacketConn, addr net.Addr, lc *finalmask.ListenConfig) (net.PacketConn, error) {
return NewConnServer(c)
func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
if level != 0 {
return nil, errors.New("xicmp requires being at the outermost level")
}
return NewConnServer(c, raw)
}
+43 -53
View File
@@ -37,17 +37,17 @@ type record struct {
}
type xicmpConnServer struct {
icmp4 *icmp.PacketConn
icmp6 *icmp.PacketConn
ips map[netip.Addr]struct{}
rec map[string]record
readCh chan packet
closeCh chan struct{}
wg sync.WaitGroup
mu sync.Mutex
conn net.PacketConn
icmp4 *icmp.PacketConn
icmp6 *icmp.PacketConn
ips map[netip.Addr]struct{}
rec map[string]record
readCh chan packet
closedCh chan struct{}
mu sync.Mutex
}
func NewConnServer(c *Config) (net.PacketConn, error) {
func NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) {
icmp4, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0")
if err != nil {
return nil, err
@@ -63,16 +63,16 @@ func NewConnServer(c *Config) (net.PacketConn, error) {
}
conn := &xicmpConnServer{
icmp4: icmp4,
icmp6: icmp6,
ips: ips,
rec: make(map[string]record),
readCh: make(chan packet),
closeCh: make(chan struct{}),
conn: raw,
icmp4: icmp4,
icmp6: icmp6,
ips: ips,
rec: make(map[string]record),
readCh: make(chan packet),
closedCh: make(chan struct{}),
}
go conn.clean()
conn.wg.Add(2)
go conn.recv4()
go conn.recv6()
@@ -81,7 +81,7 @@ func NewConnServer(c *Config) (net.PacketConn, error) {
func (c *xicmpConnServer) closed() bool {
select {
case <-c.closeCh:
case <-c.closedCh:
return true
default:
return false
@@ -102,35 +102,33 @@ func (c *xicmpConnServer) clean() {
}
}
c.mu.Unlock()
case <-c.closeCh:
case <-c.closedCh:
return
}
}
}
func (c *xicmpConnServer) recv4() {
defer c.wg.Done()
var b [finalmask.UDPSize]byte
for {
if c.closed() {
return
}
n, addr, err := c.icmp4.ReadFrom(b[:])
if err != nil {
if c.closed() {
return
}
var netErr net.Error
if goerrors.As(err, &netErr) && netErr.Timeout() {
select {
case c.readCh <- packet{
err: err,
}:
case <-c.closeCh:
case <-c.closedCh:
return
}
continue
}
errors.LogErrorInner(context.Background(), err, "recv err 4")
return
continue
}
msg, err := icmp.ParseMessage(1, b[:n])
@@ -181,7 +179,7 @@ func (c *xicmpConnServer) recv4() {
p: p,
addr: cAddr,
}:
case <-c.closeCh:
case <-c.closedCh:
pool.Put(p)
return
}
@@ -189,28 +187,26 @@ func (c *xicmpConnServer) recv4() {
}
func (c *xicmpConnServer) recv6() {
defer c.wg.Done()
var b [finalmask.UDPSize]byte
for {
if c.closed() {
return
}
n, addr, err := c.icmp6.ReadFrom(b[:])
if err != nil {
if c.closed() {
return
}
var netErr net.Error
if goerrors.As(err, &netErr) && netErr.Timeout() {
select {
case c.readCh <- packet{
err: err,
}:
case <-c.closeCh:
case <-c.closedCh:
return
}
continue
}
errors.LogErrorInner(context.Background(), err, "recv err 6")
return
continue
}
msg, err := icmp.ParseMessage(58, b[:n])
@@ -261,7 +257,7 @@ func (c *xicmpConnServer) recv6() {
p: p,
addr: cAddr,
}:
case <-c.closeCh:
case <-c.closedCh:
pool.Put(p)
return
}
@@ -269,15 +265,16 @@ func (c *xicmpConnServer) recv6() {
}
func (c *xicmpConnServer) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
packet, ok := <-c.readCh
if ok {
select {
case packet := <-c.readCh:
if packet.p != nil {
n = copy(p, packet.p)
pool.Put(packet.p)
}
return n, packet.addr, packet.err
case <-c.closedCh:
return 0, nil, io.EOF
}
return 0, nil, io.EOF
}
func (c *xicmpConnServer) WriteTo(p []byte, addr net.Addr) (n int, err error) {
@@ -313,9 +310,10 @@ func (c *xicmpConnServer) WriteTo(p []byte, addr net.Addr) (n int, err error) {
}
if err != nil {
errors.LogErrorInner(context.Background(), err, "send err")
errors.LogErrorInner(context.Background(), err, "xicmp write")
return 0, err
}
return len(p), nil
}
@@ -325,23 +323,15 @@ func (c *xicmpConnServer) Close() error {
if c.closed() {
return nil
}
close(c.closeCh)
close(c.closedCh)
_ = c.icmp4.Close()
_ = c.icmp6.Close()
c.wg.Wait()
select {
case p := <-c.readCh:
if p.p != nil {
pool.Put(p.p)
}
default:
}
close(c.readCh)
_ = c.conn.Close()
return nil
}
func (c *xicmpConnServer) LocalAddr() net.Addr {
return &net.UDPAddr{IP: []byte{0, 0, 0, 0}}
return c.conn.LocalAddr()
}
func (c *xicmpConnServer) SetDeadline(t time.Time) error {
@@ -39,19 +39,19 @@ type record struct {
}
type xicmpConnServer struct {
icmp4 *icmp.PacketConn
icmp6 *icmp.PacketConn
ipv4PC *ipv4.PacketConn
ipv6PC *ipv6.PacketConn
ips map[netip.Addr]struct{}
rec map[string]record
readCh chan packet
closeCh chan struct{}
wg sync.WaitGroup
mu sync.Mutex
conn net.PacketConn
icmp4 *icmp.PacketConn
icmp6 *icmp.PacketConn
ipv4PC *ipv4.PacketConn
ipv6PC *ipv6.PacketConn
ips map[netip.Addr]struct{}
rec map[string]record
readCh chan packet
closedCh chan struct{}
mu sync.Mutex
}
func NewConnServer(c *Config) (net.PacketConn, error) {
func NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) {
icmp4, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0")
if err != nil {
return nil, err
@@ -67,21 +67,21 @@ func NewConnServer(c *Config) (net.PacketConn, error) {
}
conn := &xicmpConnServer{
icmp4: icmp4,
icmp6: icmp6,
ipv4PC: icmp4.IPv4PacketConn(),
ipv6PC: icmp6.IPv6PacketConn(),
ips: ips,
rec: make(map[string]record),
readCh: make(chan packet),
closeCh: make(chan struct{}),
conn: raw,
icmp4: icmp4,
icmp6: icmp6,
ipv4PC: icmp4.IPv4PacketConn(),
ipv6PC: icmp6.IPv6PacketConn(),
ips: ips,
rec: make(map[string]record),
readCh: make(chan packet),
closedCh: make(chan struct{}),
}
common.Must(conn.ipv4PC.SetControlMessage(ipv4.FlagDst, true))
common.Must(conn.ipv6PC.SetControlMessage(ipv6.FlagDst, true))
go conn.clean()
conn.wg.Add(2)
go conn.recv4()
go conn.recv6()
@@ -90,7 +90,7 @@ func NewConnServer(c *Config) (net.PacketConn, error) {
func (c *xicmpConnServer) closed() bool {
select {
case <-c.closeCh:
case <-c.closedCh:
return true
default:
return false
@@ -111,35 +111,33 @@ func (c *xicmpConnServer) clean() {
}
}
c.mu.Unlock()
case <-c.closeCh:
case <-c.closedCh:
return
}
}
}
func (c *xicmpConnServer) recv4() {
defer c.wg.Done()
var b [finalmask.UDPSize]byte
for {
if c.closed() {
return
}
n, cm, addr, err := c.ipv4PC.ReadFrom(b[:])
if err != nil {
if c.closed() {
return
}
var netErr net.Error
if goerrors.As(err, &netErr) && netErr.Timeout() {
select {
case c.readCh <- packet{
err: err,
}:
case <-c.closeCh:
case <-c.closedCh:
return
}
continue
}
errors.LogErrorInner(context.Background(), err, "recv err 4")
return
continue
}
msg, err := icmp.ParseMessage(1, b[:n])
@@ -191,7 +189,7 @@ func (c *xicmpConnServer) recv4() {
p: p,
addr: cAddr,
}:
case <-c.closeCh:
case <-c.closedCh:
pool.Put(p)
return
}
@@ -199,28 +197,26 @@ func (c *xicmpConnServer) recv4() {
}
func (c *xicmpConnServer) recv6() {
defer c.wg.Done()
var b [finalmask.UDPSize]byte
for {
if c.closed() {
return
}
n, cm, addr, err := c.ipv6PC.ReadFrom(b[:])
if err != nil {
if c.closed() {
return
}
var netErr net.Error
if goerrors.As(err, &netErr) && netErr.Timeout() {
select {
case c.readCh <- packet{
err: err,
}:
case <-c.closeCh:
case <-c.closedCh:
return
}
continue
}
errors.LogErrorInner(context.Background(), err, "recv err 6")
return
continue
}
msg, err := icmp.ParseMessage(58, b[:n])
@@ -272,7 +268,7 @@ func (c *xicmpConnServer) recv6() {
p: p,
addr: cAddr,
}:
case <-c.closeCh:
case <-c.closedCh:
pool.Put(p)
return
}
@@ -280,15 +276,16 @@ func (c *xicmpConnServer) recv6() {
}
func (c *xicmpConnServer) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
packet, ok := <-c.readCh
if ok {
select {
case packet := <-c.readCh:
if packet.p != nil {
n = copy(p, packet.p)
pool.Put(packet.p)
}
return n, packet.addr, packet.err
case <-c.closedCh:
return 0, nil, io.EOF
}
return 0, nil, io.EOF
}
func (c *xicmpConnServer) WriteTo(p []byte, addr net.Addr) (n int, err error) {
@@ -324,9 +321,10 @@ func (c *xicmpConnServer) WriteTo(p []byte, addr net.Addr) (n int, err error) {
}
if err != nil {
errors.LogErrorInner(context.Background(), err, "send err")
errors.LogErrorInner(context.Background(), err, "xicmp write")
return 0, err
}
return len(p), nil
}
@@ -336,23 +334,15 @@ func (c *xicmpConnServer) Close() error {
if c.closed() {
return nil
}
close(c.closeCh)
close(c.closedCh)
_ = c.icmp4.Close()
_ = c.icmp6.Close()
c.wg.Wait()
select {
case p := <-c.readCh:
if p.p != nil {
pool.Put(p.p)
}
default:
}
close(c.readCh)
_ = c.conn.Close()
return nil
}
func (c *xicmpConnServer) LocalAddr() net.Addr {
return &net.UDPAddr{IP: []byte{0, 0, 0, 0}}
return c.conn.LocalAddr()
}
func (c *xicmpConnServer) SetDeadline(t time.Time) error {
+5 -4
View File
@@ -2,12 +2,13 @@ package xmc
import (
"fmt"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet/finalmask"
"net"
)
func (c *Config) WrapConnClient(conn net.Conn, dest *net.Destination, dialer *finalmask.Dialer) (net.Conn, error) {
func (c *Config) TCP() {
}
func (c *Config) WrapConnClient(conn net.Conn) (net.Conn, error) {
profiles, err := profilesFromConfig(c.Profiles)
if err != nil {
return nil, fmt.Errorf("minecraft finalmask: %w", err)
+11 -6
View File
@@ -83,6 +83,7 @@ func getGrpcClient(ctx context.Context, dest net.Destination, streamSettings *in
}
tlsConfig := tls.ConfigFromStreamSettings(streamSettings)
realityConfig := reality.ConfigFromStreamSettings(streamSettings)
sockopt := streamSettings.SocketSettings
grpcSettings := streamSettings.ProtocolSettings.(*Config)
if client, found := globalDialerMap[dialerConf{dest, streamSettings}]; found && client.GetState() != connectivity.Shutdown {
@@ -123,13 +124,17 @@ func getGrpcClient(ctx context.Context, dest net.Destination, streamSettings *in
gctx = session.ContextWithOutbounds(gctx, session.OutboundsFromContext(ctx))
gctx = session.ContextWithTimeoutOnly(gctx, true)
var c net.Conn
if streamSettings.FinalMask != nil {
c, err = streamSettings.FinalMask.DialTCP(gctx, net.TCPDestination(address, port))
} else {
c, err = internet.DialSystem(ctx, dest, streamSettings.SocketSettings)
}
c, err := internet.DialSystem(gctx, net.TCPDestination(address, port), sockopt)
if err == nil {
if streamSettings.TcpmaskManager != nil {
newConn, err := streamSettings.TcpmaskManager.WrapConnClient(c)
if err != nil {
c.Close()
return nil, errors.New("mask err").Base(err)
}
c = newConn
}
if tlsConfig != nil {
config := tlsConfig.GetTLSConfig(tls.WithDestination(dest))
if fingerprint := tls.GetFingerprint(tlsConfig.Fingerprint); fingerprint != nil {
+19 -11
View File
@@ -104,20 +104,28 @@ func Listen(ctx context.Context, address net.Address, port net.Port, settings *i
go func() {
var streamListener net.Listener
var err error
var addr net.Addr
if port == net.Port(0) { // unix
addr = &net.UnixAddr{Name: address.Domain(), Net: "unix"}
streamListener, err = internet.ListenSystem(ctx, &net.UnixAddr{
Name: address.Domain(),
Net: "unix",
}, settings.SocketSettings)
if err != nil {
errors.LogErrorInner(ctx, err, "failed to listen on ", address)
return
}
} else { // tcp
addr = &net.TCPAddr{IP: address.IP(), Port: int(port)}
streamListener, err = internet.ListenSystem(ctx, &net.TCPAddr{
IP: address.IP(),
Port: int(port),
}, settings.SocketSettings)
if err != nil {
errors.LogErrorInner(ctx, err, "failed to listen on ", address, ":", port)
return
}
}
if settings.FinalMask != nil {
streamListener, err = settings.FinalMask.Listen(ctx, addr)
} else {
streamListener, err = internet.ListenSystem(ctx, addr, settings.SocketSettings)
}
if err != nil {
errors.LogErrorInner(ctx, err, "failed to listen on ", address, ":", port)
return
if settings.TcpmaskManager != nil {
streamListener, _ = settings.TcpmaskManager.WrapListener(streamListener)
}
errors.LogDebug(ctx, "gRPC listen for service name `"+grpcSettings.getServiceName()+"` tun `"+grpcSettings.getTunStreamName()+"` multi tun `"+grpcSettings.getTunMultiStreamName()+"`")
+10 -7
View File
@@ -46,18 +46,21 @@ func (c *ConnRF) Read(b []byte) (int, error) {
func dialhttpUpgrade(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (net.Conn, error) {
transportConfiguration := streamSettings.ProtocolSettings.(*Config)
var pconn net.Conn
var err error
if streamSettings.FinalMask != nil {
pconn, err = streamSettings.FinalMask.DialTCP(ctx, dest)
} else {
pconn, err = internet.DialSystem(ctx, dest, streamSettings.SocketSettings)
}
pconn, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings)
if err != nil {
errors.LogErrorInner(ctx, err, "failed to dial to ", dest)
return nil, err
}
if streamSettings.TcpmaskManager != nil {
newConn, err := streamSettings.TcpmaskManager.WrapConnClient(pconn)
if err != nil {
pconn.Close()
return nil, errors.New("mask err").Base(err)
}
pconn = newConn
}
var conn net.Conn
var requestURL url.URL
tConfig := tls.ConfigFromStreamSettings(streamSettings)
+19 -11
View File
@@ -124,21 +124,29 @@ func ListenHTTPUpgrade(ctx context.Context, address net.Address, port net.Port,
}
var listener net.Listener
var err error
var addr net.Addr
if port == net.Port(0) { // unix
addr = &net.UnixAddr{Name: address.Domain(), Net: "unix"}
listener, err = internet.ListenSystem(ctx, &net.UnixAddr{
Name: address.Domain(),
Net: "unix",
}, streamSettings.SocketSettings)
if err != nil {
return nil, errors.New("failed to listen unix domain socket(for HttpUpgrade) on ", address).Base(err)
}
errors.LogInfo(ctx, "listening unix domain socket(for HttpUpgrade) on ", address)
} else { // tcp
addr = &net.TCPAddr{IP: address.IP(), Port: int(port)}
listener, err = internet.ListenSystem(ctx, &net.TCPAddr{
IP: address.IP(),
Port: int(port),
}, streamSettings.SocketSettings)
if err != nil {
return nil, errors.New("failed to listen TCP(for HttpUpgrade) on ", address, ":", port).Base(err)
}
errors.LogInfo(ctx, "listening TCP(for HttpUpgrade) on ", address, ":", port)
}
if streamSettings.FinalMask != nil {
listener, err = streamSettings.FinalMask.Listen(ctx, addr)
} else {
listener, err = internet.ListenSystem(ctx, addr, streamSettings.SocketSettings)
if streamSettings.TcpmaskManager != nil {
listener, _ = streamSettings.TcpmaskManager.WrapListener(listener)
}
if err != nil {
return nil, errors.New("failed to listen ", addr.Network(), "(for HttpUpgrade) on ", address, ":", port).Base(err)
}
errors.LogInfo(ctx, "listening ", addr.Network(), "(for HttpUpgrade) on ", address, ":", port)
if streamSettings.SocketSettings != nil && streamSettings.SocketSettings.AcceptProxyProtocol {
errors.LogWarning(ctx, "accepting PROXY protocol")
+7 -4
View File
@@ -103,11 +103,14 @@ func (c *InterConn) Update() {
func (c *InterConn) Read(p []byte) (int, error) {
b, ok := <-c.ch
if ok {
c.Update()
return copy(p, b), nil
if !ok {
return 0, io.EOF
}
return 0, io.EOF
if len(p) < len(b) {
return 0, io.ErrShortBuffer
}
c.Update()
return copy(p, b), nil
}
func (c *InterConn) Write(p []byte) (int, error) {
+63 -28
View File
@@ -2,7 +2,8 @@ package hysteria
import (
"context"
gotls "crypto/tls"
go_tls "crypto/tls"
"math/rand"
"net/http"
"net/url"
"reflect"
@@ -21,6 +22,7 @@ import (
"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/hysteria/udphop"
"github.com/xtls/xray-core/transport/internet/stat"
"github.com/xtls/xray-core/transport/internet/tls"
)
@@ -28,12 +30,12 @@ import (
type client struct {
sync.Mutex
dest net.Destination
config *Config
tlsConfig *gotls.Config
socketConfig *internet.SocketConfig
finalMask *finalmask.FinalMask
quicParams *internet.QuicParams
dest net.Destination
config *Config
tlsConfig *go_tls.Config
socketConfig *internet.SocketConfig
udpmaskManager *finalmask.UdpmaskManager
quicParams *internet.QuicParams
conn *quic.Conn
tr *quic.Transport
@@ -76,6 +78,7 @@ func (c *client) dial(ctx context.Context) error {
if quicParams == nil {
quicParams = &internet.QuicParams{
BbrProfile: string(bbr.ProfileStandard),
UdpHop: &internet.UdpHop{},
}
}
@@ -112,30 +115,62 @@ func (c *client) dial(ctx context.Context) error {
// quicConfig.KeepAlivePeriod = 10 * time.Second
// }
var pktConn net.PacketConn
var udpAddr net.Addr
if c.finalMask != nil {
conn, err := c.finalMask.DialUDP(ctx, c.dest)
udpHopDialer := func(addr *net.UDPAddr) (net.PacketConn, error) {
conn, err := internet.DialSystem(ctx, net.UDPDestination(net.IPAddress(addr.IP), net.Port(addr.Port)), c.socketConfig)
if err != nil {
return errors.New("failed to dial to dest").Base(err)
}
pktConn = conn.(*finalmask.PacketConnWrapper).PacketConn
udpAddr = conn.RemoteAddr()
} else {
conn, err := internet.DialSystem(ctx, c.dest, c.socketConfig)
if err != nil {
return errors.New("failed to dial to dest").Base(err)
errors.LogInfoInner(context.Background(), err, "skip hop: failed to dial to dest")
return nil, errors.New("")
}
var pktConn net.PacketConn
switch c := conn.(type) {
case *internet.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:
panic(reflect.TypeOf(c))
}
return pktConn, nil
}
var pktConn net.PacketConn
var udpAddr *net.UDPAddr
var index int
if len(quicParams.UdpHop.Ports) > 0 {
index = rand.Intn(len(quicParams.UdpHop.Ports))
c.dest.Port = net.Port(quicParams.UdpHop.Ports[index])
}
raw, err := internet.DialSystem(ctx, c.dest, c.socketConfig)
if err != nil {
return errors.New("failed to dial to dest").Base(err)
}
switch c := raw.(type) {
case *internet.PacketConnWrapper:
pktConn = c.PacketConn
udpAddr = raw.RemoteAddr().(*net.UDPAddr)
case *cnc.Connection:
pktConn = &internet.FakePacketConn{Conn: c}
udpAddr = &net.UDPAddr{IP: c.RemoteAddr().(*net.TCPAddr).IP, Port: c.RemoteAddr().(*net.TCPAddr).Port}
default:
panic(reflect.TypeOf(c))
}
if len(quicParams.UdpHop.Ports) > 0 {
pktConn = udphop.NewUDPHopPacketConn(udphop.ToAddrs(udpAddr.IP, quicParams.UdpHop.Ports), time.Duration(quicParams.UdpHop.IntervalMin)*time.Second, time.Duration(quicParams.UdpHop.IntervalMax)*time.Second, udpHopDialer, pktConn, index)
}
if c.udpmaskManager != nil {
newConn, err := c.udpmaskManager.WrapPacketConnClient(pktConn)
if err != nil {
pktConn.Close()
return errors.New("mask err").Base(err)
}
pktConn = newConn
}
tr := &quic.Transport{Conn: pktConn, DisableGSO: quicParams.DisableGSO}
@@ -149,7 +184,7 @@ func (c *client) dial(ctx context.Context) error {
rt := &http3.Transport{
TLSClientConfig: c.tlsConfig,
QUICConfig: quicConfig,
Dial: func(ctx context.Context, _ string, tlsCfg *gotls.Config, cfg *quic.Config) (*quic.Conn, error) {
Dial: func(ctx context.Context, _ string, tlsCfg *go_tls.Config, cfg *quic.Config) (*quic.Conn, error) {
qc, err := tr.DialEarly(ctx, udpAddr, tlsCfg, cfg)
if err != nil {
return nil, err
@@ -315,12 +350,12 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
c = manager.m[dialerConf{dest, streamSettings}]
if c == nil {
c = &client{
dest: dest,
config: streamSettings.ProtocolSettings.(*Config),
tlsConfig: tlsConfig.GetTLSConfig(tls.WithDestination(dest)),
socketConfig: streamSettings.SocketSettings,
finalMask: streamSettings.FinalMask,
quicParams: streamSettings.QuicParams,
dest: dest,
config: streamSettings.ProtocolSettings.(*Config),
tlsConfig: tlsConfig.GetTLSConfig(tls.WithDestination(dest)),
socketConfig: streamSettings.SocketSettings,
udpmaskManager: streamSettings.UdpmaskManager,
quicParams: streamSettings.QuicParams,
}
manager.m[dialerConf{dest, streamSettings}] = c
}
+11 -7
View File
@@ -281,6 +281,7 @@ func Listen(ctx context.Context, address net.Address, port net.Port, streamSetti
if quicParams == nil {
quicParams = &internet.QuicParams{
BbrProfile: string(bbr.ProfileStandard),
UdpHop: &internet.UdpHop{},
}
}
@@ -316,17 +317,20 @@ func Listen(ctx context.Context, address net.Address, port net.Port, streamSetti
quicConfig.MaxIncomingStreams = 1024
}
var pktConn net.PacketConn
var err error
if streamSettings.FinalMask != nil {
pktConn, err = streamSettings.FinalMask.ListenPacket(context.Background(), &net.UDPAddr{IP: address.IP(), Port: int(port)})
} else {
pktConn, err = internet.ListenSystemPacket(context.Background(), &net.UDPAddr{IP: address.IP(), Port: int(port)}, streamSettings.SocketSettings)
}
pktConn, err := internet.ListenSystemPacket(context.Background(), &net.UDPAddr{IP: address.IP(), Port: int(port)}, streamSettings.SocketSettings)
if err != nil {
return nil, err
}
if streamSettings.UdpmaskManager != nil {
newConn, err := streamSettings.UdpmaskManager.WrapPacketConnServer(pktConn)
if err != nil {
pktConn.Close()
return nil, errors.New("mask err").Base(err)
}
pktConn = newConn
}
var k *quic.StatelessResetKey
if !quicParams.DisableStatelessReset {
k = &quic.StatelessResetKey{}
@@ -1,165 +0,0 @@
package hysteria
import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"net"
"runtime"
"testing"
"time"
"github.com/apernet/quic-go"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/protocol/tls/cert"
)
func TestDatagram(t *testing.T) {
run := func() (addr net.Addr, recv chan int64, cancel func()) {
cert, _ := cert.MustGenerate(nil)
Certificate := [][]byte{cert.Certificate}
PrivateKey := common.Must2(x509.ParsePKCS8PrivateKey(cert.PrivateKey))
tlsConf := &tls.Config{
Certificates: []tls.Certificate{
{
Certificate: Certificate,
PrivateKey: PrivateKey,
},
},
NextProtos: []string{"h3"},
}
quicConf := &quic.Config{
InitialStreamReceiveWindow: 8388608,
MaxStreamReceiveWindow: 8388608,
InitialConnectionReceiveWindow: 8388608 * 5 / 2,
MaxConnectionReceiveWindow: 8388608 * 5 / 2,
MaxIdleTimeout: 30 * time.Second,
MaxIncomingStreams: 1024,
DisablePathMTUDiscovery: runtime.GOOS != "linux" && runtime.GOOS != "windows" && runtime.GOOS != "darwin",
EnableDatagrams: true,
MaxDatagramFrameSize: MaxDatagramFrameSize,
AssumePeerMaxDatagramFrameSize: MaxDatagramFrameSize,
DisablePathManager: true,
}
pktConn := common.Must2(net.ListenPacket("udp", "127.0.0.1:0"))
tr := &quic.Transport{Conn: pktConn}
l := common.Must2(tr.Listen(tlsConf, quicConf))
recv = make(chan int64)
ctx, cancel := context.WithCancel(context.Background())
go func() {
defer pktConn.Close()
defer tr.Close()
defer l.Close()
defer close(recv)
var buf [1500]byte
for {
conn, err := l.Accept(ctx)
if err != nil {
if !errors.Is(err, context.Canceled) {
t.Error(err)
}
break
}
err = conn.SendDatagram(buf[:])
var qErr *quic.DatagramTooLargeError
if !errors.As(err, &qErr) {
t.Error(err)
}
recv <- qErr.MaxDatagramPayloadSize
defer conn.CloseWithError(0, "")
}
}()
return l.Addr(), recv, cancel
}
addr, recv, cancel := run()
t.Run("With ChromeParrot", func(t *testing.T) {
tlsConf := &tls.Config{
InsecureSkipVerify: true,
}
quicConf := &quic.Config{
InitialStreamReceiveWindow: 8388608,
MaxStreamReceiveWindow: 8388608,
InitialConnectionReceiveWindow: 8388608 * 5 / 2,
MaxConnectionReceiveWindow: 8388608 * 5 / 2,
MaxIdleTimeout: 30 * time.Second,
KeepAlivePeriod: 10 * time.Second,
DisablePathMTUDiscovery: runtime.GOOS != "linux" && runtime.GOOS != "windows" && runtime.GOOS != "darwin",
ChromeParrot: true,
EnableDatagrams: true,
MaxDatagramFrameSize: MaxDatagramFrameSize,
OmitMaxDatagramFrameSize: true,
DisablePathManager: true,
}
pktConn := common.Must2(net.ListenPacket("udp", "127.0.0.1:0"))
tr := &quic.Transport{Conn: pktConn, ConnectionIDGenerator: quic.ZeroLengthConnectionIDGenerator{}}
conn := common.Must2(tr.DialEarly(context.Background(), addr, tlsConf, quicConf))
defer pktConn.Close()
defer tr.Close()
defer conn.CloseWithError(0, "")
var buf [1500]byte
err := conn.SendDatagram(buf[:])
var qErr *quic.DatagramTooLargeError
if !errors.As(err, &qErr) || qErr.MaxDatagramPayloadSize != 1197 {
t.Error(err)
}
if server := <-recv; server != 1243 {
t.Error(server)
}
})
t.Run("Without ChromeParrot", func(t *testing.T) {
tlsConf := &tls.Config{
InsecureSkipVerify: true,
NextProtos: []string{"h3"},
}
quicConf := &quic.Config{
InitialStreamReceiveWindow: 8388608,
MaxStreamReceiveWindow: 8388608,
InitialConnectionReceiveWindow: 8388608 * 5 / 2,
MaxConnectionReceiveWindow: 8388608 * 5 / 2,
MaxIdleTimeout: 30 * time.Second,
KeepAlivePeriod: 10 * time.Second,
DisablePathMTUDiscovery: runtime.GOOS != "linux" && runtime.GOOS != "windows" && runtime.GOOS != "darwin",
ChromeParrot: false,
EnableDatagrams: true,
MaxDatagramFrameSize: MaxDatagramFrameSize,
OmitMaxDatagramFrameSize: true,
DisablePathManager: true,
}
pktConn := common.Must2(net.ListenPacket("udp", "127.0.0.1:0"))
tr := &quic.Transport{Conn: pktConn}
conn := common.Must2(tr.DialEarly(context.Background(), addr, tlsConf, quicConf))
defer pktConn.Close()
defer tr.Close()
defer conn.CloseWithError(0, "")
var buf [1500]byte
err := conn.SendDatagram(buf[:])
var qErr *quic.DatagramTooLargeError
if !errors.As(err, &qErr) || qErr.MaxDatagramPayloadSize != 1197 {
t.Error(err)
}
if server := <-recv; server != 1197 {
t.Error(server)
}
})
cancel()
}
+254
View File
@@ -0,0 +1,254 @@
package udphop
import (
"errors"
"math/rand"
"net"
"sync"
"time"
"github.com/xtls/xray-core/transport/internet/finalmask"
)
const (
packetQueueSize = 1024
udpBufferSize = finalmask.UDPSize
defaultHopInterval = 30 * time.Second
)
type UdpHopPacketConn struct {
Addrs []net.Addr
HopIntervalMin time.Duration
HopIntervalMax time.Duration
ListenUDPFunc func(addr *net.UDPAddr) (net.PacketConn, error)
connMutex sync.RWMutex
prevConn net.PacketConn
currentConn net.PacketConn
addrIndex int
deadline time.Time
readDeadline time.Time
writeDeadline time.Time
recvQueue chan *udpPacket
closeChan chan struct{}
closed bool
bufPool sync.Pool
}
type udpPacket struct {
Buf []byte
N int
Addr net.Addr
Err error
}
func NewUDPHopPacketConn(addrs []net.Addr, hopIntervalMin time.Duration, hopIntervalMax time.Duration, listenUDPFunc func(addr *net.UDPAddr) (net.PacketConn, error), currentConn net.PacketConn, addrIndex int) net.PacketConn {
if len(addrs) == 0 {
panic("len(addrs) == 0")
}
if hopIntervalMin == 0 {
hopIntervalMin = defaultHopInterval
}
if hopIntervalMax == 0 {
hopIntervalMax = defaultHopInterval
}
if hopIntervalMin < 5*time.Second {
panic("hopIntervalMin < 5*time.Second")
}
if hopIntervalMax < 5*time.Second {
panic("hopIntervalMax < 5*time.Second")
}
if hopIntervalMax < hopIntervalMin {
panic("hopIntervalMax < hopIntervalMin")
}
if listenUDPFunc == nil {
panic("listenUDPFunc is nil")
}
hConn := &UdpHopPacketConn{
Addrs: addrs,
HopIntervalMin: hopIntervalMin,
HopIntervalMax: hopIntervalMax,
ListenUDPFunc: listenUDPFunc,
prevConn: nil,
currentConn: currentConn,
addrIndex: addrIndex,
recvQueue: make(chan *udpPacket, packetQueueSize),
closeChan: make(chan struct{}),
bufPool: sync.Pool{
New: func() interface{} {
return make([]byte, udpBufferSize)
},
},
}
go hConn.recvLoop(hConn.currentConn)
go hConn.hopLoop()
return hConn
}
func (u *UdpHopPacketConn) recvLoop(conn net.PacketConn) {
for {
buf := u.bufPool.Get().([]byte)
n, addr, err := conn.ReadFrom(buf)
if err != nil {
u.bufPool.Put(buf)
var netErr net.Error
if errors.As(err, &netErr) && netErr.Timeout() {
u.recvQueue <- &udpPacket{nil, 0, nil, netErr}
continue
}
return
}
select {
case u.recvQueue <- &udpPacket{buf, n, addr, nil}:
default:
u.bufPool.Put(buf)
}
}
}
func (u *UdpHopPacketConn) hopLoop() {
timer := time.NewTimer(u.nextHopInterval())
defer timer.Stop()
for {
select {
case <-timer.C:
u.hop()
timer.Reset(u.nextHopInterval())
case <-u.closeChan:
return
}
}
}
func (u *UdpHopPacketConn) nextHopInterval() time.Duration {
if u.HopIntervalMin == u.HopIntervalMax {
return u.HopIntervalMin
}
return u.HopIntervalMin + time.Duration(rand.Int63n(int64(u.HopIntervalMax-u.HopIntervalMin)+1))
}
func (u *UdpHopPacketConn) hop() {
u.connMutex.Lock()
defer u.connMutex.Unlock()
if u.closed {
return
}
addrIndex := rand.Intn(len(u.Addrs))
newConn, err := u.ListenUDPFunc(u.Addrs[addrIndex].(*net.UDPAddr))
if err != nil {
return
}
if u.prevConn != nil {
_ = u.prevConn.Close()
}
u.prevConn = u.currentConn
u.addrIndex = addrIndex
u.currentConn = newConn
if !u.deadline.IsZero() {
_ = u.currentConn.SetDeadline(u.deadline)
}
if !u.readDeadline.IsZero() {
_ = u.currentConn.SetReadDeadline(u.readDeadline)
}
if !u.writeDeadline.IsZero() {
_ = u.currentConn.SetWriteDeadline(u.writeDeadline)
}
go u.recvLoop(newConn)
}
func (u *UdpHopPacketConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
for {
select {
case p := <-u.recvQueue:
if p.Err != nil {
return 0, nil, p.Err
}
n := copy(b, p.Buf[:p.N])
u.bufPool.Put(p.Buf)
return n, p.Addr, nil
case <-u.closeChan:
return 0, nil, net.ErrClosed
}
}
}
func (u *UdpHopPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
u.connMutex.RLock()
defer u.connMutex.RUnlock()
if u.closed {
return 0, net.ErrClosed
}
return u.currentConn.WriteTo(b, u.Addrs[u.addrIndex])
}
func (u *UdpHopPacketConn) Close() error {
u.connMutex.Lock()
defer u.connMutex.Unlock()
if u.closed {
return nil
}
if u.prevConn != nil {
_ = u.prevConn.Close()
}
err := u.currentConn.Close()
close(u.closeChan)
u.closed = true
u.Addrs = nil
return err
}
func (u *UdpHopPacketConn) LocalAddr() net.Addr {
u.connMutex.RLock()
defer u.connMutex.RUnlock()
return u.currentConn.LocalAddr()
}
func (u *UdpHopPacketConn) SetDeadline(t time.Time) error {
u.connMutex.Lock()
defer u.connMutex.Unlock()
u.deadline = t
u.readDeadline = t
u.writeDeadline = t
if u.prevConn != nil {
_ = u.prevConn.SetDeadline(t)
}
return u.currentConn.SetDeadline(t)
}
func (u *UdpHopPacketConn) SetReadDeadline(t time.Time) error {
u.connMutex.Lock()
defer u.connMutex.Unlock()
u.deadline = time.Time{}
u.readDeadline = t
if u.prevConn != nil {
_ = u.prevConn.SetReadDeadline(t)
}
return u.currentConn.SetReadDeadline(t)
}
func (u *UdpHopPacketConn) SetWriteDeadline(t time.Time) error {
u.connMutex.Lock()
defer u.connMutex.Unlock()
u.deadline = time.Time{}
u.writeDeadline = t
if u.prevConn != nil {
_ = u.prevConn.SetWriteDeadline(t)
}
return u.currentConn.SetWriteDeadline(t)
}
func ToAddrs(ip net.IP, ports []uint32) []net.Addr {
var addrs []net.Addr
for _, port := range ports {
addr := &net.UDPAddr{
IP: ip,
Port: int(port),
}
addrs = append(addrs, addr)
}
return addrs
}
+28 -7
View File
@@ -3,6 +3,7 @@ package kcp
import (
"context"
"io"
reflect "reflect"
"sync/atomic"
"github.com/xtls/xray-core/common"
@@ -10,6 +11,7 @@ import (
"github.com/xtls/xray-core/common/dice"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/net/cnc"
"github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/stat"
"github.com/xtls/xray-core/transport/internet/tls"
@@ -49,17 +51,36 @@ func DialKCP(ctx context.Context, dest net.Destination, streamSettings *internet
dest.Network = net.Network_UDP
errors.LogInfo(ctx, "dialing mKCP to ", dest)
var conn net.Conn
var err error
if streamSettings.FinalMask != nil {
conn, err = streamSettings.FinalMask.DialUDP(ctx, dest)
} else {
conn, err = internet.DialSystem(ctx, dest, streamSettings.SocketSettings)
}
conn, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings)
if err != nil {
return nil, errors.New("failed to dial to dest: ", err).AtWarning().Base(err)
}
if streamSettings.UdpmaskManager != nil {
var pktConn net.PacketConn
var udpAddr *net.UDPAddr
switch c := conn.(type) {
case *internet.PacketConnWrapper:
pktConn = c.PacketConn
udpAddr = c.RemoteAddr().(*net.UDPAddr)
case *cnc.Connection:
pktConn = &internet.FakePacketConn{Conn: c}
udpAddr = &net.UDPAddr{IP: c.RemoteAddr().(*net.TCPAddr).IP, Port: c.RemoteAddr().(*net.TCPAddr).Port}
default:
panic(reflect.TypeOf(c))
}
newConn, err := streamSettings.UdpmaskManager.WrapPacketConnClient(pktConn)
if err != nil {
pktConn.Close()
return nil, errors.New("mask err").Base(err)
}
pktConn = newConn
conn = &internet.PacketConnWrapper{
PacketConn: pktConn,
Dest: udpAddr,
}
}
kcpSettings := streamSettings.ProtocolSettings.(*Config)
reader := &KCPPacketReader{}
-18
View File
@@ -1,18 +0,0 @@
package masque
import (
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/transport/internet"
)
const protocolName = "masque"
const DefaultPath = "/.well-known/masque/ip/*/*/"
func init() {
common.Must(internet.RegisterProtocolConfigCreator(protocolName, func() interface{} {
return &Config{
Path: DefaultPath,
}
}))
}
-146
View File
@@ -1,146 +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/masque/config.proto
package masque
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type Config struct {
state protoimpl.MessageState `protogen:"open.v1"`
Host string `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"`
Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"`
Headers map[string]string `protobuf:"bytes,3,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *Config) Reset() {
*x = Config{}
mi := &file_transport_internet_masque_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_masque_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_masque_config_proto_rawDescGZIP(), []int{0}
}
func (x *Config) GetHost() string {
if x != nil {
return x.Host
}
return ""
}
func (x *Config) GetPath() string {
if x != nil {
return x.Path
}
return ""
}
func (x *Config) GetHeaders() map[string]string {
if x != nil {
return x.Headers
}
return nil
}
var File_transport_internet_masque_config_proto protoreflect.FileDescriptor
const file_transport_internet_masque_config_proto_rawDesc = "" +
"\n" +
"&transport/internet/masque/config.proto\x12\x1exray.transport.internet.masque\"\xbb\x01\n" +
"\x06Config\x12\x12\n" +
"\x04host\x18\x01 \x01(\tR\x04host\x12\x12\n" +
"\x04path\x18\x02 \x01(\tR\x04path\x12M\n" +
"\aheaders\x18\x03 \x03(\v23.xray.transport.internet.masque.Config.HeadersEntryR\aheaders\x1a:\n" +
"\fHeadersEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B|\n" +
"\"com.xray.transport.internet.masqueP\x01Z3github.com/xtls/xray-core/transport/internet/masque\xaa\x02\x1eXray.Transport.Internet.Masqueb\x06proto3"
var (
file_transport_internet_masque_config_proto_rawDescOnce sync.Once
file_transport_internet_masque_config_proto_rawDescData []byte
)
func file_transport_internet_masque_config_proto_rawDescGZIP() []byte {
file_transport_internet_masque_config_proto_rawDescOnce.Do(func() {
file_transport_internet_masque_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_masque_config_proto_rawDesc), len(file_transport_internet_masque_config_proto_rawDesc)))
})
return file_transport_internet_masque_config_proto_rawDescData
}
var file_transport_internet_masque_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_transport_internet_masque_config_proto_goTypes = []any{
(*Config)(nil), // 0: xray.transport.internet.masque.Config
nil, // 1: xray.transport.internet.masque.Config.HeadersEntry
}
var file_transport_internet_masque_config_proto_depIdxs = []int32{
1, // 0: xray.transport.internet.masque.Config.headers:type_name -> xray.transport.internet.masque.Config.HeadersEntry
1, // [1:1] is the sub-list for method output_type
1, // [1:1] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
}
func init() { file_transport_internet_masque_config_proto_init() }
func file_transport_internet_masque_config_proto_init() {
if File_transport_internet_masque_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_masque_config_proto_rawDesc), len(file_transport_internet_masque_config_proto_rawDesc)),
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_transport_internet_masque_config_proto_goTypes,
DependencyIndexes: file_transport_internet_masque_config_proto_depIdxs,
MessageInfos: file_transport_internet_masque_config_proto_msgTypes,
}.Build()
File_transport_internet_masque_config_proto = out.File
file_transport_internet_masque_config_proto_goTypes = nil
file_transport_internet_masque_config_proto_depIdxs = nil
}
-13
View File
@@ -1,13 +0,0 @@
syntax = "proto3";
package xray.transport.internet.masque;
option csharp_namespace = "Xray.Transport.Internet.Masque";
option go_package = "github.com/xtls/xray-core/transport/internet/masque";
option java_package = "com.xray.transport.internet.masque";
option java_multiple_files = true;
message Config {
string host = 1;
string path = 2;
map<string, string> headers = 3;
}
-115
View File
@@ -1,115 +0,0 @@
package masque
import (
"context"
go_errors "errors"
"net/netip"
"slices"
"sync"
"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/net"
"github.com/xtls/xray-core/transport/internet/masque/connectip"
)
type PacketTooBigError struct {
ICMP []byte
}
func (e *PacketTooBigError) Error() string {
return "packet too big for the tunnel"
}
type Conn struct {
ipConn *connectip.Conn
quicConn *quic.Conn
local []netip.Addr
closeOnce sync.Once
}
func (c *Conn) LocalAddrs() []netip.Addr {
return c.local
}
func (c *Conn) Read(b []byte) (int, error) {
return c.ipConn.ReadPacket(b)
}
func (c *Conn) Write(b []byte) (int, error) {
icmp, err := c.ipConn.WritePacket(b)
if err != nil {
if go_errors.Is(err, connectip.ErrMTUTooSmall) {
errors.LogWarning(context.Background(), "MASQUE: closing the tunnel as it cannot carry ", MinPacketSize, "-byte packets")
} else {
errors.LogInfoInner(context.Background(), err, "MASQUE: closing the tunnel as sending failed")
}
c.Close()
return 0, err
}
if len(icmp) > 0 {
return 0, &PacketTooBigError{ICMP: icmp}
}
return len(b), nil
}
func (c *Conn) Close() error {
c.closeOnce.Do(func() {
c.ipConn.Close()
c.quicConn.CloseWithError(quic.ApplicationErrorCode(http3.ErrCodeNoError), "")
})
return nil
}
func (c *Conn) LocalAddr() net.Addr {
return c.quicConn.LocalAddr()
}
func (c *Conn) RemoteAddr() net.Addr {
return c.quicConn.RemoteAddr()
}
func (c *Conn) SetDeadline(time.Time) error {
return nil
}
func (c *Conn) SetReadDeadline(time.Time) error {
return nil
}
func (c *Conn) SetWriteDeadline(time.Time) error {
return nil
}
func (c *Conn) serveAddressAssignments() {
for {
assigned, err := c.ipConn.ReceiveAddressAssignment(context.Background())
if err != nil {
return
}
for _, addr := range c.local {
if !slices.ContainsFunc(assigned, func(a connectip.AssignedAddress) bool { return !a.Rejected() && a.IPPrefix.Contains(addr) }) {
errors.LogInfo(context.Background(), "MASQUE: closing the tunnel as the proxy withdrew ", addr)
c.Close()
return
}
}
if len(localAddrs(assigned)) > len(c.local) {
errors.LogInfo(context.Background(), "MASQUE: the proxy assigned another IP family, which is used once the tunnel is set up again")
}
}
}
func (c *Conn) serveAddressRequests() {
for {
req, err := c.ipConn.ReceiveAddressRequest(context.Background())
if err != nil {
return
}
if err := req.Respond(make([]netip.Prefix, len(req.Prefixes)), nil); err != nil {
return
}
}
}
@@ -1,7 +0,0 @@
Copyright 2024 Marten Seemann
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -1,86 +0,0 @@
/* SPDX-License-Identifier: MIT
*
* Copyright 2024 Marten Seemann
* Adapted from github.com/quic-go/connect-ip-go (commit a0c35fa).
*/
package connectip
import (
"errors"
"fmt"
"net/netip"
"slices"
"sync/atomic"
)
var (
rejectedIPv4Prefix = netip.PrefixFrom(netip.IPv4Unspecified(), 32)
rejectedIPv6Prefix = netip.PrefixFrom(netip.IPv6Unspecified(), 128)
)
type AddressRequestID uint64
type AddressRequest struct {
Prefixes []netip.Prefix
conn *Conn
requested *addressRequestCapsule
responded *atomic.Bool
}
func newAddressRequest(conn *Conn, requested *addressRequestCapsule) *AddressRequest {
return &AddressRequest{
Prefixes: slices.Clone(requested.Prefixes),
conn: conn,
requested: requested,
responded: &atomic.Bool{},
}
}
func (r *AddressRequest) Respond(assignments, additional []netip.Prefix) error {
if r.conn == nil {
return errors.New("connect-ip: invalid address request")
}
if len(assignments) != len(r.requested.RequestIDs) {
return fmt.Errorf(
"connect-ip: expected %d address assignments, got %d",
len(r.requested.RequestIDs),
len(assignments),
)
}
capsule := &addressAssignCapsule{
AssignedAddresses: make([]AssignedAddress, 0, len(assignments)+len(additional)),
}
var zeroPrefix netip.Prefix
for i, p := range assignments {
if p == zeroPrefix {
if r.requested.Prefixes[i].Addr().Is4() {
p = rejectedIPv4Prefix
} else {
p = rejectedIPv6Prefix
}
} else if !p.IsValid() || p != p.Masked() {
return fmt.Errorf("connect-ip: invalid assigned prefix %d: %s", i, p)
}
capsule.AssignedAddresses = append(
capsule.AssignedAddresses,
AssignedAddress{RequestID: r.requested.RequestIDs[i], IPPrefix: p},
)
}
for i, p := range additional {
if !p.IsValid() || p != p.Masked() {
return fmt.Errorf("connect-ip: invalid additional prefix %d: %s", i, p)
}
capsule.AssignedAddresses = append(capsule.AssignedAddresses, AssignedAddress{IPPrefix: p})
}
if !r.responded.CompareAndSwap(false, true) {
return errors.New("connect-ip: address request already answered")
}
restrictPeer := slices.ContainsFunc(capsule.AssignedAddresses, func(a AssignedAddress) bool { return !a.Rejected() })
if err := r.conn.sendAddressAssignment(capsule, restrictPeer); err != nil {
r.responded.Store(false)
return err
}
return nil
}
@@ -1,85 +0,0 @@
/* SPDX-License-Identifier: MIT
*
* Copyright 2024 Marten Seemann
* Adapted from github.com/quic-go/connect-ip-go (commit a0c35fa).
*/
package connectip
import (
"context"
"net/netip"
"testing"
"time"
"github.com/stretchr/testify/require"
)
func TestAddressRequests(t *testing.T) {
client, server := setupConns(t)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
prefixes := []netip.Prefix{
netip.MustParsePrefix("0.0.0.0/32"),
netip.MustParsePrefix("0.0.0.0/32"),
netip.MustParsePrefix("::/64"),
}
ids, err := client.RequestAddresses(prefixes)
require.NoError(t, err)
require.Equal(t, []AddressRequestID{1, 2, 3}, ids)
req, err := server.ReceiveAddressRequest(ctx)
require.NoError(t, err)
require.Equal(t, prefixes, req.Prefixes)
assignments := []netip.Prefix{netip.MustParsePrefix("192.0.2.1/32"), {}, {}}
additional := []netip.Prefix{netip.MustParsePrefix("2001:db8::/64")}
require.NoError(t, req.Respond(assignments, additional))
received, err := client.ReceiveAddressAssignment(ctx)
require.NoError(t, err)
require.Len(t, received, 4)
require.Equal(t, AssignedAddress{RequestID: ids[0], IPPrefix: assignments[0]}, received[0])
require.Equal(t, ids[1], received[1].RequestID)
require.True(t, received[1].Rejected())
require.Equal(t, ids[2], received[2].RequestID)
require.True(t, received[2].Rejected())
require.Equal(t, AssignedAddress{IPPrefix: additional[0]}, received[3])
ids, err = client.RequestAddresses(prefixes[:1])
require.NoError(t, err)
require.Equal(t, []AddressRequestID{4}, ids)
}
func TestAddressRequestValidation(t *testing.T) {
conn := newProxiedConn(&mockStream{})
defer conn.Close()
for _, prefixes := range [][]netip.Prefix{
nil,
{{}},
{netip.MustParsePrefix("192.0.2.1/24")},
{netip.MustParsePrefix("2001:db8::1/64")},
} {
ids, err := conn.RequestAddresses(prefixes)
require.Error(t, err)
require.Nil(t, ids)
}
}
func TestAddressResponseValidation(t *testing.T) {
conn := newProxiedConn(&mockStream{})
defer conn.Close()
prefixes := []netip.Prefix{netip.MustParsePrefix("192.0.2.1/32")}
req := newAddressRequest(conn, &addressRequestCapsule{RequestIDs: []AddressRequestID{1}, Prefixes: prefixes})
require.ErrorContains(t, (&AddressRequest{}).Respond(nil, nil), "invalid address request")
require.ErrorContains(t, req.Respond(nil, nil), "expected 1 address assignments")
require.ErrorContains(t, req.Respond(prefixes, []netip.Prefix{{}}), "invalid additional prefix")
require.ErrorContains(t,
req.Respond([]netip.Prefix{netip.MustParsePrefix("192.0.2.1/24")}, nil),
"invalid assigned prefix",
)
copied := *req
require.NoError(t, req.Respond(prefixes, nil))
require.ErrorContains(t, copied.Respond(prefixes, nil), "already answered")
}
@@ -1,308 +0,0 @@
/* SPDX-License-Identifier: MIT
*
* Copyright 2024 Marten Seemann
* Adapted from github.com/quic-go/connect-ip-go (commit a0c35fa).
*/
package connectip
import (
"cmp"
"encoding/binary"
"errors"
"fmt"
"io"
"net/netip"
"github.com/apernet/quic-go/http3"
"github.com/apernet/quic-go/quicvarint"
)
const (
capsuleTypeDatagram http3.CapsuleType = 0
capsuleTypeAddressAssign http3.CapsuleType = 1
capsuleTypeAddressRequest http3.CapsuleType = 2
capsuleTypeRouteAdvertisement http3.CapsuleType = 3
)
const (
maxAddressesPerCapsule = 8192
maxRoutesPerCapsule = 8192
)
type addressAssignCapsule struct {
AssignedAddresses []AssignedAddress
}
type AssignedAddress struct {
RequestID AddressRequestID
IPPrefix netip.Prefix
}
func (a AssignedAddress) Rejected() bool {
return a.IPPrefix == rejectedIPv4Prefix || a.IPPrefix == rejectedIPv6Prefix
}
func (a AssignedAddress) len() int {
return quicvarint.Len(uint64(a.RequestID)) + 1 + a.IPPrefix.Addr().BitLen()/8 + 1
}
type addressRequestCapsule struct {
RequestIDs []AddressRequestID
Prefixes []netip.Prefix
}
func parseAddressAssignCapsule(r http3.CapsuleReader) (*addressAssignCapsule, error) {
var assignedAddresses []AssignedAddress
for r.Remaining() > 0 {
if len(assignedAddresses) >= maxAddressesPerCapsule {
return nil, fmt.Errorf("%w: ADDRESS_ASSIGN capsule contains too many addresses (maximum %d)", errCapsuleLimit, maxAddressesPerCapsule)
}
requestID, prefix, err := parseAddress(r)
if err != nil {
return nil, err
}
assignedAddresses = append(assignedAddresses, AssignedAddress{RequestID: AddressRequestID(requestID), IPPrefix: prefix})
}
return &addressAssignCapsule{AssignedAddresses: assignedAddresses}, nil
}
func (c *addressAssignCapsule) append(b []byte) []byte {
totalLen := 0
for _, addr := range c.AssignedAddresses {
totalLen += addr.len()
}
b = quicvarint.Append(b, uint64(capsuleTypeAddressAssign))
b = quicvarint.Append(b, uint64(totalLen))
for _, addr := range c.AssignedAddresses {
b = quicvarint.Append(b, uint64(addr.RequestID))
if addr.IPPrefix.Addr().Is4() {
b = append(b, 4)
} else {
b = append(b, 6)
}
b = append(b, addr.IPPrefix.Addr().AsSlice()...)
b = append(b, byte(addr.IPPrefix.Bits()))
}
return b
}
func parseAddressRequestCapsule(r http3.CapsuleReader) (*addressRequestCapsule, error) {
if r.Remaining() == 0 {
return nil, errors.New("ADDRESS_REQUEST capsule contains no addresses")
}
capsule := &addressRequestCapsule{}
for r.Remaining() > 0 {
if len(capsule.Prefixes) >= maxAddressesPerCapsule {
return nil, fmt.Errorf("%w: ADDRESS_REQUEST capsule contains too many addresses (maximum %d)", errCapsuleLimit, maxAddressesPerCapsule)
}
requestID, prefix, err := parseAddress(r)
if err != nil {
return nil, err
}
if requestID == 0 {
return nil, errors.New("ADDRESS_REQUEST capsule contains a zero request ID")
}
capsule.RequestIDs = append(capsule.RequestIDs, AddressRequestID(requestID))
capsule.Prefixes = append(capsule.Prefixes, prefix)
}
return capsule, nil
}
func (c *addressRequestCapsule) append(b []byte) []byte {
var totalLen int
for i, p := range c.Prefixes {
totalLen += quicvarint.Len(uint64(c.RequestIDs[i])) + 1 + p.Addr().BitLen()/8 + 1
}
b = quicvarint.Append(b, uint64(capsuleTypeAddressRequest))
b = quicvarint.Append(b, uint64(totalLen))
for i, p := range c.Prefixes {
b = quicvarint.Append(b, uint64(c.RequestIDs[i]))
if p.Addr().Is4() {
b = append(b, 4)
} else {
b = append(b, 6)
}
b = append(b, p.Addr().AsSlice()...)
b = append(b, byte(p.Bits()))
}
return b
}
func parseAddress(r io.Reader) (requestID uint64, prefix netip.Prefix, _ error) {
vr := quicvarint.NewReader(r)
requestID, err := quicvarint.Read(vr)
if err != nil {
return 0, netip.Prefix{}, err
}
ipVersion, err := vr.ReadByte()
if err != nil {
return 0, netip.Prefix{}, err
}
var ip netip.Addr
switch ipVersion {
case 4:
var ipv4 [4]byte
if _, err := io.ReadFull(r, ipv4[:]); err != nil {
return 0, netip.Prefix{}, err
}
ip = netip.AddrFrom4(ipv4)
case 6:
var ipv6 [16]byte
if _, err := io.ReadFull(r, ipv6[:]); err != nil {
return 0, netip.Prefix{}, err
}
ip = netip.AddrFrom16(ipv6)
default:
return 0, netip.Prefix{}, fmt.Errorf("invalid IP version: %d", ipVersion)
}
prefixLen, err := vr.ReadByte()
if err != nil {
return 0, netip.Prefix{}, err
}
if int(prefixLen) > ip.BitLen() {
return 0, netip.Prefix{}, fmt.Errorf("prefix length %d exceeds IP address length (%d)", prefixLen, ip.BitLen())
}
prefix = netip.PrefixFrom(ip, int(prefixLen))
if prefix != prefix.Masked() {
return 0, netip.Prefix{}, errors.New("lower bits not covered by prefix length are not all zero")
}
return requestID, prefix, nil
}
type routeAdvertisementCapsule struct {
IPAddressRanges []IPRoute
}
type IPRoute struct {
StartIP netip.Addr
EndIP netip.Addr
IPProtocol uint8
}
func (r IPRoute) len() int { return 1 + r.StartIP.BitLen()/8 + r.EndIP.BitLen()/8 + 1 }
func (r IPRoute) Prefixes() []netip.Prefix { return rangeToPrefixes(r.StartIP, r.EndIP) }
func parseRouteAdvertisementCapsule(r http3.CapsuleReader) (*routeAdvertisementCapsule, error) {
var ranges []IPRoute
for r.Remaining() > 0 {
if len(ranges) >= maxRoutesPerCapsule {
return nil, fmt.Errorf("%w: ROUTE_ADVERTISEMENT capsule contains too many routes (maximum %d)", errCapsuleLimit, maxRoutesPerCapsule)
}
ipRange, err := parseIPAddressRange(r)
if err != nil {
return nil, err
}
if len(ranges) > 0 {
if err := checkRouteOrder(ranges[len(ranges)-1], ipRange); err != nil {
return nil, err
}
}
ranges = append(ranges, ipRange)
}
return &routeAdvertisementCapsule{IPAddressRanges: ranges}, nil
}
func (r IPRoute) validate() error {
if !r.StartIP.IsValid() || !r.EndIP.IsValid() || r.StartIP.Zone() != "" || r.EndIP.Zone() != "" {
return fmt.Errorf("invalid IP address range %s-%s", r.StartIP, r.EndIP)
}
if r.StartIP.Is4() != r.EndIP.Is4() {
return fmt.Errorf("IP address range %s-%s mixes IP versions", r.StartIP, r.EndIP)
}
if r.StartIP.Compare(r.EndIP) > 0 {
return fmt.Errorf("start IP %s is greater than end IP %s", r.StartIP, r.EndIP)
}
return nil
}
func checkRouteOrder(a, b IPRoute) error {
switch cmp.Or(
cmp.Compare(a.StartIP.BitLen(), b.StartIP.BitLen()),
cmp.Compare(a.IPProtocol, b.IPProtocol),
) {
case 1:
return fmt.Errorf("routes are not ordered by IP version and IP protocol: %s-%s (protocol %d) precedes %s-%s (protocol %d)",
a.StartIP, a.EndIP, a.IPProtocol, b.StartIP, b.EndIP, b.IPProtocol)
case 0:
if a.EndIP.Compare(b.StartIP) >= 0 {
return fmt.Errorf("IP address ranges %s-%s and %s-%s (protocol %d) overlap or are not in ascending order",
a.StartIP, a.EndIP, b.StartIP, b.EndIP, b.IPProtocol)
}
}
return nil
}
func (c *routeAdvertisementCapsule) append(b []byte) []byte {
var totalLen int
for _, ipRange := range c.IPAddressRanges {
totalLen += ipRange.len()
}
b = quicvarint.Append(b, uint64(capsuleTypeRouteAdvertisement))
b = quicvarint.Append(b, uint64(totalLen))
for _, ipRange := range c.IPAddressRanges {
if ipRange.StartIP.Is4() {
b = append(b, 4)
} else {
b = append(b, 6)
}
b = append(b, ipRange.StartIP.AsSlice()...)
b = append(b, ipRange.EndIP.AsSlice()...)
b = append(b, ipRange.IPProtocol)
}
return b
}
func parseIPAddressRange(r io.Reader) (IPRoute, error) {
var ipVersion uint8
if err := binary.Read(r, binary.LittleEndian, &ipVersion); err != nil {
return IPRoute{}, err
}
var startIP, endIP netip.Addr
switch ipVersion {
case 4:
var start, end [4]byte
if _, err := io.ReadFull(r, start[:]); err != nil {
return IPRoute{}, err
}
if _, err := io.ReadFull(r, end[:]); err != nil {
return IPRoute{}, err
}
startIP = netip.AddrFrom4(start)
endIP = netip.AddrFrom4(end)
case 6:
var start, end [16]byte
if _, err := io.ReadFull(r, start[:]); err != nil {
return IPRoute{}, err
}
if _, err := io.ReadFull(r, end[:]); err != nil {
return IPRoute{}, err
}
startIP = netip.AddrFrom16(start)
endIP = netip.AddrFrom16(end)
default:
return IPRoute{}, fmt.Errorf("invalid IP version: %d", ipVersion)
}
if startIP.Compare(endIP) > 0 {
return IPRoute{}, errors.New("start IP is greater than end IP")
}
var ipProtocol uint8
if err := binary.Read(r, binary.LittleEndian, &ipProtocol); err != nil {
return IPRoute{}, err
}
return IPRoute{
StartIP: startIP,
EndIP: endIP,
IPProtocol: ipProtocol,
}, nil
}
@@ -1,449 +0,0 @@
/* SPDX-License-Identifier: MIT
*
* Copyright 2024 Marten Seemann
* Adapted from github.com/quic-go/connect-ip-go (commit a0c35fa).
*/
package connectip
import (
"bytes"
"context"
"io"
"net"
"net/netip"
"testing"
"time"
"github.com/apernet/quic-go/http3"
"github.com/apernet/quic-go/quicvarint"
"github.com/stretchr/testify/require"
)
func newCapsuleReader(t *testing.T, typ http3.CapsuleType, payload []byte) http3.CapsuleReader {
t.Helper()
data := quicvarint.Append(nil, uint64(typ))
data = quicvarint.Append(data, uint64(len(payload)))
data = append(data, payload...)
parsedType, cr, err := http3.NewCapsuleParser(bytes.NewReader(data)).Next()
require.NoError(t, err)
require.Equal(t, typ, parsedType)
return cr
}
func testIncompleteCapsule(t *testing.T, data []byte, parse func(http3.CapsuleReader) error) {
t.Helper()
r := bytes.NewReader(data)
_, cr, err := http3.NewCapsuleParser(r).Next()
require.NoError(t, err)
require.NoError(t, parse(cr))
require.Zero(t, r.Len())
for i := range data {
_, cr, err := http3.NewCapsuleParser(bytes.NewReader(data[:i])).Next()
if err != nil {
if i == 0 {
require.ErrorIs(t, err, io.EOF)
} else {
require.ErrorIs(t, err, io.ErrUnexpectedEOF)
}
continue
}
require.ErrorIs(t, parse(cr), io.ErrUnexpectedEOF)
}
}
func testCapsuleEntryLimit[T any](t *testing.T, typ http3.CapsuleType, limit int, entry func(i int) []byte, parse func(http3.CapsuleReader) (*T, error)) {
t.Helper()
var payload []byte
for i := range limit {
payload = append(payload, entry(i)...)
}
r := newCapsuleReader(t, typ, payload)
_, err := parse(r)
require.NoError(t, err)
require.Zero(t, r.Remaining())
data := quicvarint.Append(nil, uint64(typ))
data = quicvarint.Append(data, uint64(len(payload)+1))
_, r, err = http3.NewCapsuleParser(bytes.NewReader(append(data, payload...))).Next()
require.NoError(t, err)
_, err = parse(r)
require.ErrorContains(t, err, "too many")
require.Equal(t, int64(1), r.Remaining())
}
func TestParseAddressAssignCapsule(t *testing.T) {
addr1 := quicvarint.Append(nil, 1337)
addr1 = append(addr1, 4)
addr1 = append(addr1, netip.AddrFrom4([4]byte{1, 2, 3, 0}).AsSlice()...)
addr1 = append(addr1, 24)
addr2 := quicvarint.Append(nil, 1338)
addr2 = append(addr2, 6)
addr2 = append(addr2, netip.MustParseAddr("2001:db8::1").AsSlice()...)
addr2 = append(addr2, 128)
data := quicvarint.Append(nil, uint64(capsuleTypeAddressAssign))
data = quicvarint.Append(data, uint64(len(addr1)+len(addr2)))
data = append(data, addr1...)
data = append(data, addr2...)
r := bytes.NewReader(data)
typ, cr, err := http3.NewCapsuleParser(r).Next()
require.NoError(t, err)
require.Equal(t, capsuleTypeAddressAssign, typ)
capsule, err := parseAddressAssignCapsule(cr)
require.NoError(t, err)
require.Equal(t,
[]AssignedAddress{
{RequestID: 1337, IPPrefix: netip.MustParsePrefix("1.2.3.0/24")},
{RequestID: 1338, IPPrefix: netip.MustParsePrefix("2001:db8::1/128")},
},
capsule.AssignedAddresses,
)
require.Zero(t, r.Len())
}
func TestParseAddressAssignCapsuleLimit(t *testing.T) {
entry := []byte{1, 4, 192, 0, 2, 1, 32}
testCapsuleEntryLimit(t, capsuleTypeAddressAssign, maxAddressesPerCapsule, func(int) []byte { return entry }, parseAddressAssignCapsule)
}
func TestAssignedAddressRejected(t *testing.T) {
for _, prefix := range []string{"0.0.0.0/32", "::/128"} {
require.True(t, (AssignedAddress{RequestID: 1, IPPrefix: netip.MustParsePrefix(prefix)}).Rejected())
}
for _, prefix := range []string{"0.0.0.0/0", "0.0.0.0/31", "::/0", "::/127", "192.0.2.1/32", "2001:db8::1/128"} {
require.False(t, (AssignedAddress{RequestID: 1, IPPrefix: netip.MustParsePrefix(prefix)}).Rejected())
}
require.False(t, (AssignedAddress{}).Rejected())
}
func TestWriteAddressAssignCapsule(t *testing.T) {
c := &addressAssignCapsule{
AssignedAddresses: []AssignedAddress{
{RequestID: 1337, IPPrefix: netip.MustParsePrefix("1.2.3.0/24")},
{RequestID: 1338, IPPrefix: netip.MustParsePrefix("2001:db8::1/128")},
},
}
data := c.append(nil)
r := bytes.NewReader(data)
typ, cr, err := http3.NewCapsuleParser(r).Next()
require.NoError(t, err)
require.Equal(t, capsuleTypeAddressAssign, typ)
parsed, err := parseAddressAssignCapsule(cr)
require.NoError(t, err)
require.Equal(t, c, parsed)
require.Zero(t, r.Len())
}
func TestParseAddressAssignCapsuleInvalid(t *testing.T) {
testParseAddressCapsuleInvalid(t, capsuleTypeAddressAssign, func(r http3.CapsuleReader) error {
_, err := parseAddressAssignCapsule(r)
return err
})
}
func testParseAddressCapsuleInvalid(t *testing.T, typ http3.CapsuleType, f func(r http3.CapsuleReader) error) {
t.Run("invalid IP version", func(t *testing.T) {
addr1 := quicvarint.Append(nil, 1337)
addr1 = append(addr1, 5)
addr1 = append(addr1, netip.AddrFrom4([4]byte{1, 2, 3, 4}).AsSlice()...)
addr1 = append(addr1, 32)
require.ErrorContains(t, f(newCapsuleReader(t, typ, addr1)), "invalid IP version: 5")
})
t.Run("invalid prefix length", func(t *testing.T) {
addr1 := quicvarint.Append(nil, 1337)
addr1 = append(addr1, 4)
addr1 = append(addr1, netip.AddrFrom4([4]byte{1, 2, 3, 4}).AsSlice()...)
addr1 = append(addr1, 33)
require.ErrorContains(t, f(newCapsuleReader(t, typ, addr1)), "prefix length 33 exceeds IP address length (32)")
})
t.Run("lower bits not covered by prefix length are not all zero", func(t *testing.T) {
addr1 := quicvarint.Append(nil, 1337)
addr1 = append(addr1, 4)
addr1 = append(addr1, netip.AddrFrom4([4]byte{1, 2, 3, 4}).AsSlice()...)
addr1 = append(addr1, 28)
require.ErrorContains(t, f(newCapsuleReader(t, typ, addr1)), "lower bits not covered by prefix length are not all zero")
})
t.Run("incomplete capsule", func(t *testing.T) {
var data []byte
switch typ {
case capsuleTypeAddressAssign:
data = (&addressAssignCapsule{
AssignedAddresses: []AssignedAddress{
{RequestID: 1337, IPPrefix: netip.MustParsePrefix("1.2.3.4/32")},
{RequestID: 1338, IPPrefix: netip.MustParsePrefix("2001:db8::1/128")},
},
}).append(nil)
case capsuleTypeAddressRequest:
data = (&addressRequestCapsule{
RequestIDs: []AddressRequestID{1337, 1338},
Prefixes: []netip.Prefix{netip.MustParsePrefix("1.2.3.4/32"), netip.MustParsePrefix("2001:db8::1/128")},
}).append(nil)
default:
t.Fatalf("unexpected capsule type: %d", typ)
}
testIncompleteCapsule(t, data, f)
})
}
func TestParseAddressRequestCapsule(t *testing.T) {
addr1 := quicvarint.Append(nil, 1337)
addr1 = append(addr1, 4)
addr1 = append(addr1, netip.AddrFrom4([4]byte{1, 2, 3, 0}).AsSlice()...)
addr1 = append(addr1, 24)
addr2 := quicvarint.Append(nil, 1338)
addr2 = append(addr2, 6)
addr2 = append(addr2, netip.MustParseAddr("2001:db8::1").AsSlice()...)
addr2 = append(addr2, 128)
data := quicvarint.Append(nil, uint64(capsuleTypeAddressRequest))
data = quicvarint.Append(data, uint64(len(addr1)+len(addr2)))
data = append(data, addr1...)
data = append(data, addr2...)
r := bytes.NewReader(data)
typ, cr, err := http3.NewCapsuleParser(r).Next()
require.NoError(t, err)
require.Equal(t, capsuleTypeAddressRequest, typ)
capsule, err := parseAddressRequestCapsule(cr)
require.NoError(t, err)
require.Equal(t, []AddressRequestID{1337, 1338}, capsule.RequestIDs)
require.Equal(t, []netip.Prefix{netip.MustParsePrefix("1.2.3.0/24"), netip.MustParsePrefix("2001:db8::1/128")}, capsule.Prefixes)
require.Zero(t, r.Len())
}
func TestParseAddressRequestCapsuleLimit(t *testing.T) {
entry := []byte{1, 4, 192, 0, 2, 1, 32}
testCapsuleEntryLimit(t, capsuleTypeAddressRequest, maxAddressesPerCapsule, func(int) []byte { return entry }, parseAddressRequestCapsule)
}
func TestWriteAddressRequestCapsule(t *testing.T) {
c := &addressRequestCapsule{
RequestIDs: []AddressRequestID{1337, 1338},
Prefixes: []netip.Prefix{netip.MustParsePrefix("1.2.3.0/24"), netip.MustParsePrefix("2001:db8::1/128")},
}
data := c.append(nil)
r := bytes.NewReader(data)
typ, cr, err := http3.NewCapsuleParser(r).Next()
require.NoError(t, err)
require.Equal(t, capsuleTypeAddressRequest, typ)
parsed, err := parseAddressRequestCapsule(cr)
require.NoError(t, err)
require.Equal(t, c, parsed)
require.Zero(t, r.Len())
}
func TestParseAddressRequestCapsuleInvalid(t *testing.T) {
t.Run("empty", func(t *testing.T) {
_, err := parseAddressRequestCapsule(newCapsuleReader(t, capsuleTypeAddressRequest, nil))
require.ErrorContains(t, err, "contains no addresses")
})
t.Run("zero request ID", func(t *testing.T) {
_, err := parseAddressRequestCapsule(newCapsuleReader(t, capsuleTypeAddressRequest, []byte{0, 4, 192, 0, 2, 1, 32}))
require.ErrorContains(t, err, "zero request ID")
})
testParseAddressCapsuleInvalid(t, capsuleTypeAddressRequest, func(r http3.CapsuleReader) error {
_, err := parseAddressRequestCapsule(r)
return err
})
}
func TestParseRouteAdvertisementCapsule(t *testing.T) {
iprange1 := []byte{4}
iprange1 = append(iprange1, netip.AddrFrom4([4]byte{1, 1, 1, 1}).AsSlice()...)
iprange1 = append(iprange1, netip.AddrFrom4([4]byte{1, 2, 3, 4}).AsSlice()...)
iprange1 = append(iprange1, 13)
iprange2 := []byte{6}
iprange2 = append(iprange2, netip.MustParseAddr("2001:db8::1").AsSlice()...)
iprange2 = append(iprange2, netip.MustParseAddr("2001:db8::100").AsSlice()...)
iprange2 = append(iprange2, 37)
data := quicvarint.Append(nil, uint64(capsuleTypeRouteAdvertisement))
data = quicvarint.Append(data, uint64(len(iprange1)+len(iprange2)))
data = append(data, iprange1...)
data = append(data, iprange2...)
r := bytes.NewReader(data)
typ, cr, err := http3.NewCapsuleParser(r).Next()
require.NoError(t, err)
require.Equal(t, capsuleTypeRouteAdvertisement, typ)
capsule, err := parseRouteAdvertisementCapsule(cr)
require.NoError(t, err)
require.Equal(t,
[]IPRoute{
{StartIP: netip.MustParseAddr("1.1.1.1"), EndIP: netip.MustParseAddr("1.2.3.4"), IPProtocol: 13},
{StartIP: netip.MustParseAddr("2001:db8::1"), EndIP: netip.MustParseAddr("2001:db8::100"), IPProtocol: 37},
},
capsule.IPAddressRanges,
)
require.Equal(t,
rangeToPrefixes(netip.MustParseAddr("1.1.1.1"), netip.MustParseAddr("1.2.3.4")),
capsule.IPAddressRanges[0].Prefixes(),
)
require.Equal(t,
rangeToPrefixes(netip.MustParseAddr("2001:db8::1"), netip.MustParseAddr("2001:db8::100")),
capsule.IPAddressRanges[1].Prefixes(),
)
require.Zero(t, r.Len())
}
func TestParseRouteAdvertisementCapsuleLimit(t *testing.T) {
entry := func(i int) []byte { return []byte{4, 10, 0, byte(i >> 8), byte(i), 10, 0, byte(i >> 8), byte(i), 0} }
testCapsuleEntryLimit(t, capsuleTypeRouteAdvertisement, maxRoutesPerCapsule, entry, parseRouteAdvertisementCapsule)
}
func TestWriteRouteAdvertisementCapsule(t *testing.T) {
c := &routeAdvertisementCapsule{
IPAddressRanges: []IPRoute{
{StartIP: netip.MustParseAddr("1.1.1.1"), EndIP: netip.MustParseAddr("1.2.3.4"), IPProtocol: 13},
{StartIP: netip.MustParseAddr("2001:db8::1"), EndIP: netip.MustParseAddr("2001:db8::100"), IPProtocol: 37},
},
}
data := c.append(nil)
r := bytes.NewReader(data)
typ, cr, err := http3.NewCapsuleParser(r).Next()
require.NoError(t, err)
require.Equal(t, capsuleTypeRouteAdvertisement, typ)
parsed, err := parseRouteAdvertisementCapsule(cr)
require.NoError(t, err)
require.Equal(t, c, parsed)
require.Zero(t, r.Len())
}
func TestParseRouteAdvertisementCapsuleInvalid(t *testing.T) {
t.Run("invalid IP version", func(t *testing.T) {
iprange1 := []byte{5}
iprange1 = append(iprange1, netip.AddrFrom4([4]byte{1, 1, 1, 1}).AsSlice()...)
iprange1 = append(iprange1, netip.AddrFrom4([4]byte{1, 1, 1, 2}).AsSlice()...)
iprange1 = append(iprange1, 13)
_, err := parseRouteAdvertisementCapsule(newCapsuleReader(t, capsuleTypeRouteAdvertisement, iprange1))
require.ErrorContains(t, err, "invalid IP version: 5")
})
t.Run("start IP is greater than end IP", func(t *testing.T) {
iprange1 := []byte{4}
iprange1 = append(iprange1, netip.AddrFrom4([4]byte{1, 2, 3, 4}).AsSlice()...)
iprange1 = append(iprange1, netip.AddrFrom4([4]byte{1, 1, 1, 1}).AsSlice()...)
iprange1 = append(iprange1, 13)
_, err := parseRouteAdvertisementCapsule(newCapsuleReader(t, capsuleTypeRouteAdvertisement, iprange1))
require.ErrorContains(t, err, "start IP is greater than end IP")
})
t.Run("incomplete capsule", func(t *testing.T) {
data := (&routeAdvertisementCapsule{
IPAddressRanges: []IPRoute{
{StartIP: netip.MustParseAddr("1.1.1.1"), EndIP: netip.MustParseAddr("2.2.2.2"), IPProtocol: 13},
{StartIP: netip.MustParseAddr("2001:db8::1"), EndIP: netip.MustParseAddr("2001:db8::100"), IPProtocol: 37},
},
}).append(nil)
testIncompleteCapsule(t, data, func(r http3.CapsuleReader) error {
_, err := parseRouteAdvertisementCapsule(r)
return err
})
})
}
var (
route4a = IPRoute{StartIP: netip.MustParseAddr("10.0.0.0"), EndIP: netip.MustParseAddr("10.0.0.9")}
route4b = IPRoute{StartIP: netip.MustParseAddr("10.0.0.10"), EndIP: netip.MustParseAddr("10.0.0.20")}
route4ab = IPRoute{StartIP: netip.MustParseAddr("10.0.0.9"), EndIP: netip.MustParseAddr("10.0.0.20")}
route6 = IPRoute{StartIP: netip.MustParseAddr("2001:db8::"), EndIP: netip.MustParseAddr("2001:db8::ffff")}
)
func withProtocol(r IPRoute, proto uint8) IPRoute {
r.IPProtocol = proto
return r
}
var routeOrderTests = []struct {
name string
routes []IPRoute
err string
}{
{name: "empty"},
{name: "adjacent ranges", routes: []IPRoute{route4a, route4b}},
{name: "IPv4 before IPv6 with a lower IP protocol", routes: []IPRoute{withProtocol(route4a, 17), route6}},
{name: "same range for different IP protocols", routes: []IPRoute{withProtocol(route4a, 6), withProtocol(route4a, 17)}},
{name: "IP protocol order before address order", routes: []IPRoute{withProtocol(route4b, 6), withProtocol(route4a, 17)}},
{name: "IPv6 before IPv4", routes: []IPRoute{route6, route4a}, err: "not ordered by IP version and IP protocol"},
{name: "descending IP protocols", routes: []IPRoute{withProtocol(route4a, 17), withProtocol(route4b, 6)}, err: "not ordered by IP version and IP protocol"},
{name: "descending ranges", routes: []IPRoute{route4b, route4a}, err: "overlap or are not in ascending order"},
{name: "overlapping ranges", routes: []IPRoute{route4a, route4ab}, err: "overlap or are not in ascending order"},
{name: "duplicate range", routes: []IPRoute{route6, route6}, err: "overlap or are not in ascending order"},
}
func TestParseRouteAdvertisementCapsuleOrder(t *testing.T) {
for _, tc := range routeOrderTests {
t.Run(tc.name, func(t *testing.T) {
data := (&routeAdvertisementCapsule{IPAddressRanges: tc.routes}).append(nil)
_, cr, err := http3.NewCapsuleParser(bytes.NewReader(data)).Next()
require.NoError(t, err)
capsule, err := parseRouteAdvertisementCapsule(cr)
if tc.err != "" {
require.ErrorContains(t, err, tc.err)
return
}
require.NoError(t, err)
require.Equal(t, tc.routes, capsule.IPAddressRanges)
})
}
}
func TestAdvertiseRouteValidation(t *testing.T) {
tests := []struct {
name string
routes []IPRoute
err string
}{
{name: "invalid start IP", routes: []IPRoute{{EndIP: route4a.EndIP}}, err: "invalid IP address range"},
{name: "invalid end IP", routes: []IPRoute{{StartIP: route4a.StartIP}}, err: "invalid IP address range"},
{
name: "IPv6 zone",
routes: []IPRoute{{StartIP: netip.MustParseAddr("fe80::1%eth0"), EndIP: netip.MustParseAddr("fe80::2%eth0")}},
err: "invalid IP address range",
},
{name: "mixed IP versions", routes: []IPRoute{{StartIP: route4a.StartIP, EndIP: route6.EndIP}}, err: "mixes IP versions"},
{
name: "IPv4 and IPv4-mapped IPv6",
routes: []IPRoute{{StartIP: netip.MustParseAddr("10.0.0.1"), EndIP: netip.MustParseAddr("::ffff:10.0.0.2")}},
err: "mixes IP versions",
},
{name: "start after end", routes: []IPRoute{route4a, {StartIP: route4b.EndIP, EndIP: route4b.StartIP}}, err: "invalid route 1: start IP 10.0.0.20 is greater than end IP 10.0.0.10"},
}
tests = append(tests, routeOrderTests...)
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
conn := newProxiedConn(&mockStream{})
t.Cleanup(func() { conn.Close() })
err := conn.AdvertiseRoute(tc.routes)
if tc.err != "" {
require.ErrorContains(t, err, tc.err)
conn.mu.Lock()
defer conn.mu.Unlock()
require.Empty(t, conn.queuedWrites)
require.Nil(t, conn.localRoutes)
return
}
require.NoError(t, err)
})
}
}
func TestReceiveMisorderedRouteAdvertisement(t *testing.T) {
toRead := make(chan []byte, 1)
conn := newProxiedConn(&mockStream{toRead: toRead})
t.Cleanup(func() { conn.Close() })
toRead <- (&routeAdvertisementCapsule{IPAddressRanges: []IPRoute{route6, route4a}}).append(nil)
ctx, cancel := context.WithTimeout(t.Context(), time.Second)
defer cancel()
_, err := conn.Routes(ctx)
require.ErrorIs(t, err, net.ErrClosed)
}
@@ -1,95 +0,0 @@
/* SPDX-License-Identifier: MIT
*
* Copyright 2024 Marten Seemann
* Adapted from github.com/quic-go/connect-ip-go (commit a0c35fa).
*/
package connectip
import (
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"log"
"math/big"
"time"
"github.com/apernet/quic-go/http3"
)
var (
tlsConf *tls.Config
certPool *x509.CertPool
)
func generateCA() (*x509.Certificate, *rsa.PrivateKey, error) {
certTempl := &x509.Certificate{
SerialNumber: big.NewInt(2019),
Subject: pkix.Name{},
NotBefore: time.Now(),
NotAfter: time.Now().Add(24 * time.Hour),
IsCA: true,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
BasicConstraintsValid: true,
}
caPrivateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, nil, err
}
caBytes, err := x509.CreateCertificate(rand.Reader, certTempl, certTempl, &caPrivateKey.PublicKey, caPrivateKey)
if err != nil {
return nil, nil, err
}
ca, err := x509.ParseCertificate(caBytes)
if err != nil {
return nil, nil, err
}
return ca, caPrivateKey, nil
}
func generateLeafCert(ca *x509.Certificate, caPrivateKey *rsa.PrivateKey) (*x509.Certificate, *rsa.PrivateKey, error) {
certTempl := &x509.Certificate{
SerialNumber: big.NewInt(1),
DNSNames: []string{"localhost", "127.0.0.1"},
NotBefore: time.Now(),
NotAfter: time.Now().Add(24 * time.Hour),
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature,
}
privKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, nil, err
}
certBytes, err := x509.CreateCertificate(rand.Reader, certTempl, ca, &privKey.PublicKey, caPrivateKey)
if err != nil {
return nil, nil, err
}
cert, err := x509.ParseCertificate(certBytes)
if err != nil {
return nil, nil, err
}
return cert, privKey, nil
}
func init() {
ca, caPrivateKey, err := generateCA()
if err != nil {
log.Fatal("failed to generate CA certificate:", err)
}
leafCert, leafPrivateKey, err := generateLeafCert(ca, caPrivateKey)
if err != nil {
log.Fatal("failed to generate leaf certificate:", err)
}
certPool = x509.NewCertPool()
certPool.AddCert(ca)
tlsConf = &tls.Config{
Certificates: []tls.Certificate{{
Certificate: [][]byte{leafCert.Raw},
PrivateKey: leafPrivateKey,
}},
NextProtos: []string{http3.NextProtoH3},
}
}
@@ -1,23 +0,0 @@
/* SPDX-License-Identifier: MIT
*
* Copyright 2024 Marten Seemann
* Adapted from github.com/quic-go/connect-ip-go (commit a0c35fa).
*/
package connectip
import "encoding/binary"
func calculateIPv4Checksum(header []byte) uint16 {
var sum uint32
for i := 0; i < len(header); i += 2 {
if i == 10 {
continue
}
sum += uint32(binary.BigEndian.Uint16(header[i : i+2]))
}
for (sum >> 16) > 0 {
sum = (sum & 0xffff) + (sum >> 16)
}
return ^uint16(sum)
}
@@ -1,27 +0,0 @@
/* SPDX-License-Identifier: MIT
*
* Copyright 2024 Marten Seemann
* Adapted from github.com/quic-go/connect-ip-go (commit a0c35fa).
*/
package connectip
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestIPv4ChecksumTestVector(t *testing.T) {
data := []byte{0x45, 0x00, 0x00, 0x73, 0x00, 0x00, 0x40, 0x00, 0x40, 0x11, 0xb8, 0x61, 0xc0, 0xa8, 0x00, 0x01, 0xc0, 0xa8, 0x00, 0xc7}
checksum := calculateIPv4Checksum(data)
require.Equal(t, uint16(0xb861), checksum)
}
func TestIPv4ChecksumWithOptions(t *testing.T) {
data := []byte{0x46, 0x00, 0x00, 0x77, 0x00, 0x00, 0x40, 0x00, 0x40, 0x11, 0x00, 0x00, 0xc0, 0xa8, 0x00, 0x01, 0xc0, 0xa8, 0x00, 0xc7, 0x94, 0x04, 0x00, 0x00}
checksum := calculateIPv4Checksum(data)
data[10], data[11] = byte(checksum>>8), byte(checksum)
require.True(t, ipv4ChecksumValid(data))
require.NotEqual(t, checksum, calculateIPv4Checksum(data[:20]), "the options must be covered")
}
@@ -1,75 +0,0 @@
/* SPDX-License-Identifier: MIT
*
* Copyright 2024 Marten Seemann
* Adapted from github.com/quic-go/connect-ip-go (commit a0c35fa).
*/
package connectip
import (
"context"
"errors"
"fmt"
"net/http"
"github.com/apernet/quic-go"
"github.com/apernet/quic-go/http3"
)
type ClientConn struct {
clientConn *http3.ClientConn
}
func NewClientConn(conn *http3.ClientConn) *ClientConn {
return &ClientConn{clientConn: conn}
}
func (c *ClientConn) Dial(req *Request) (*Conn, *http.Response, error) {
httpReq := req.httpRequest()
if httpReq.URL == nil {
return nil, nil, errors.New("connect-ip: request URL is nil")
}
if httpReq.Host == "" && httpReq.URL.Host == "" {
return nil, nil, errors.New("connect-ip: request needs a host")
}
select {
case <-httpReq.Context().Done():
return nil, nil, context.Cause(httpReq.Context())
case <-c.clientConn.Context().Done():
return nil, nil, context.Cause(c.clientConn.Context())
case <-c.clientConn.ReceivedSettings():
}
settings := c.clientConn.Settings()
if !settings.EnableExtendedConnect {
return nil, nil, errors.New("connect-ip: server didn't enable Extended CONNECT")
}
if !settings.EnableDatagrams {
return nil, nil, errors.New("connect-ip: server didn't enable datagrams")
}
rstr, err := c.clientConn.OpenRequestStream(httpReq.Context())
if err != nil {
return nil, nil, fmt.Errorf("connect-ip: failed to open request stream: %w", err)
}
var keepStream bool
defer func() {
if !keepStream {
rstr.CancelRead(quic.StreamErrorCode(http3.ErrCodeNoError))
rstr.CancelWrite(quic.StreamErrorCode(http3.ErrCodeNoError))
}
}()
if err := rstr.SendRequestHeader(httpReq); err != nil {
return nil, nil, fmt.Errorf("connect-ip: failed to send request: %w", err)
}
rsp, err := rstr.ReadResponse()
if err != nil {
return nil, nil, fmt.Errorf("connect-ip: failed to read response: %w", err)
}
if rsp.StatusCode < 200 || rsp.StatusCode > 299 {
return nil, rsp, fmt.Errorf("connect-ip: server responded with %d", rsp.StatusCode)
}
keepStream = true
return newProxiedConn(rstr), rsp, nil
}
@@ -1,101 +0,0 @@
/* SPDX-License-Identifier: MIT
*
* Copyright 2024 Marten Seemann
* Adapted from github.com/quic-go/connect-ip-go (commit a0c35fa).
*/
package connectip
import (
"context"
"net"
"net/http"
"testing"
"time"
"github.com/apernet/quic-go"
"github.com/apernet/quic-go/http3"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestClientWaitForSettings(t *testing.T) {
conn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0})
require.NoError(t, err)
ln, err := quic.Listen(conn, tlsConf, &quic.Config{EnableDatagrams: true})
require.NoError(t, err)
defer ln.Close()
h3conn := dialHTTP3(t, conn.LocalAddr().String())
ctx, cancel := context.WithTimeout(t.Context(), 100*time.Millisecond)
defer cancel()
req, err := NewRequest(ctx, "https://example.org/.well-known/masque/ip/")
require.NoError(t, err)
_, _, err = NewClientConn(h3conn).Dial(req)
require.ErrorIs(t, err, context.DeadlineExceeded)
}
func TestClientDatagramCheck(t *testing.T) {
s := http3.Server{
TLSConfig: tlsConf,
QUICConfig: &quic.Config{EnableDatagrams: true},
EnableDatagrams: false,
}
ln, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0})
require.NoError(t, err)
go func() { s.Serve(ln) }()
defer s.Close()
h3conn := dialHTTP3(t, ln.LocalAddr().String())
ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
defer cancel()
req, err := NewRequest(ctx, "https://example.org/.well-known/masque/ip/")
require.NoError(t, err)
_, _, err = NewClientConn(h3conn).Dial(req)
require.ErrorContains(t, err, "connect-ip: server didn't enable datagrams")
}
func TestNewClientConnSharesHTTP3Connection(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
ln, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1)})
require.NoError(t, err)
defer ln.Close()
url := "https://" + ln.LocalAddr().String()
mux := http.NewServeMux()
mux.HandleFunc("/connect-ip", func(w http.ResponseWriter, r *http.Request) {
req, err := ParseProxyRequest(r)
if !assert.NoError(t, err) {
w.WriteHeader(http.StatusBadRequest)
return
}
_, err = (&Proxy{}).Proxy(w, req)
assert.NoError(t, err)
})
mux.HandleFunc("GET /hello", func(http.ResponseWriter, *http.Request) {})
s := http3.Server{Handler: mux, TLSConfig: tlsConf, EnableDatagrams: true}
go func() { s.Serve(ln) }()
defer s.Close()
h3conn := dialHTTP3(t, ln.LocalAddr().String())
httpClient := &http.Client{Transport: h3conn, Timeout: time.Second}
checkHTTP := func() {
t.Helper()
rsp, err := httpClient.Get(url + "/hello")
require.NoError(t, err)
rsp.Body.Close()
require.Equal(t, http.StatusOK, rsp.StatusCode)
}
checkHTTP()
req, err := NewRequest(ctx, url+"/connect-ip")
require.NoError(t, err)
tunnel, rsp, err := NewClientConn(h3conn).Dial(req)
require.NoError(t, err)
require.Equal(t, http.StatusOK, rsp.StatusCode)
checkHTTP()
require.NoError(t, tunnel.Close())
checkHTTP()
}
-620
View File
@@ -1,620 +0,0 @@
/* SPDX-License-Identifier: MIT
*
* Copyright 2024 Marten Seemann
* Adapted from github.com/quic-go/connect-ip-go (commit a0c35fa).
*/
package connectip
import (
"context"
"encoding/binary"
goerrors "errors"
"fmt"
"io"
"net"
"net/netip"
"slices"
"sync"
"time"
"github.com/apernet/quic-go"
"github.com/apernet/quic-go/http3"
"github.com/apernet/quic-go/quicvarint"
"github.com/xtls/xray-core/common/errors"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
)
type CloseError struct {
Remote bool
}
func (e *CloseError) Error() string { return net.ErrClosed.Error() }
func (e *CloseError) Is(target error) bool { return target == net.ErrClosed }
const (
ipProtoICMP = 1
ipProtoICMPv6 = 58
)
type http3Stream interface {
io.ReadWriteCloser
StreamID() quic.StreamID
ReceiveDatagram(context.Context) ([]byte, error)
SendDatagram([]byte) error
CancelRead(quic.StreamErrorCode)
CancelWrite(quic.StreamErrorCode)
SetWriteDeadline(time.Time) error
}
var (
_ http3Stream = &http3.Stream{}
_ http3Stream = &http3.RequestStream{}
)
const maxQueuedCapsules = 128
var errCapsuleLimit = goerrors.New("connect-ip: capsule limit exceeded")
type streamWrite struct {
Data []byte
Fin bool
}
type Conn struct {
str http3Stream
writeNotify chan struct{}
writeDone chan error
assignedAddressUpdates chan []AssignedAddress
addressRequests chan *addressRequestCapsule
availableRouteUpdates chan []IPRoute
mu sync.Mutex
queuedWrites []streamWrite
peerAddresses []netip.Prefix
localRoutes []IPRoute
assignedAddresses []netip.Prefix
lastAddressRequestID AddressRequestID
closeChan chan struct{}
closeErr error
closeOnce sync.Once
closeResult error
datagramCapsuleOnce sync.Once
}
func newProxiedConn(str http3Stream) *Conn {
c := &Conn{
str: str,
writeNotify: make(chan struct{}, 1),
writeDone: make(chan error, 1),
assignedAddressUpdates: make(chan []AssignedAddress, maxQueuedCapsules),
addressRequests: make(chan *addressRequestCapsule, maxQueuedCapsules),
availableRouteUpdates: make(chan []IPRoute, 1),
closeChan: make(chan struct{}),
}
go func() {
err := c.readFromStream()
c.mu.Lock()
closing := c.closeErr != nil
if !closing {
c.closeErr = &CloseError{Remote: true}
close(c.closeChan)
if err != nil {
code := http3.ErrCodeMessageError
var streamErr *quic.StreamError
var h3Err *http3.Error
switch {
case goerrors.Is(err, errCapsuleLimit):
code = http3.ErrCodeExcessiveLoad
case goerrors.As(err, &streamErr) && streamErr.Remote, goerrors.As(err, &h3Err) && h3Err.Remote:
code = http3.ErrCodeRequestCanceled
}
c.str.CancelRead(quic.StreamErrorCode(code))
c.str.CancelWrite(quic.StreamErrorCode(code))
close(c.writeNotify)
} else {
c.queueFin()
}
}
c.mu.Unlock()
if err != nil && !closing {
errors.LogInfoInner(context.Background(), err, "reading capsules failed")
}
}()
go func() {
err := c.writeToStream()
if err != nil {
c.mu.Lock()
closing := c.closeErr != nil
if !closing {
c.closeErr = &CloseError{Remote: true}
close(c.closeChan)
c.str.CancelRead(quic.StreamErrorCode(http3.ErrCodeExcessiveLoad))
c.str.CancelWrite(quic.StreamErrorCode(http3.ErrCodeExcessiveLoad))
} else {
c.str.CancelWrite(quic.StreamErrorCode(http3.ErrCodeNoError))
}
c.mu.Unlock()
if !closing {
errors.LogInfoInner(context.Background(), err, "writing capsules failed")
}
}
c.writeDone <- err
close(c.writeDone)
}()
return c
}
func (c *Conn) AdvertiseRoute(routes []IPRoute) error {
for i, route := range routes {
err := route.validate()
if err == nil && i > 0 {
err = checkRouteOrder(routes[i-1], route)
}
if err != nil {
return fmt.Errorf("connect-ip: invalid route %d: %w", i, err)
}
}
c.mu.Lock()
if c.closeErr != nil {
err := c.closeErr
c.mu.Unlock()
return err
}
routes = slices.Clone(routes)
err := c.queueWrite(streamWrite{Data: (&routeAdvertisementCapsule{IPAddressRanges: routes}).append(nil)})
if err == nil {
c.localRoutes = routes
}
c.mu.Unlock()
if err != nil {
c.Close()
return err
}
return nil
}
func (c *Conn) RequestAddresses(prefixes []netip.Prefix) ([]AddressRequestID, error) {
if len(prefixes) == 0 {
return nil, goerrors.New("connect-ip: address request must contain at least one prefix")
}
for i, p := range prefixes {
if !p.IsValid() || p != p.Masked() {
return nil, fmt.Errorf("connect-ip: invalid requested prefix %d: %s", i, p)
}
}
c.mu.Lock()
if c.closeErr != nil {
err := c.closeErr
c.mu.Unlock()
return nil, err
}
ids := make([]AddressRequestID, len(prefixes))
for i := range ids {
ids[i] = c.lastAddressRequestID + AddressRequestID(i) + 1
}
capsule := &addressRequestCapsule{RequestIDs: ids, Prefixes: prefixes}
err := c.queueWrite(streamWrite{Data: capsule.append(nil)})
if err == nil {
c.lastAddressRequestID = ids[len(ids)-1]
}
c.mu.Unlock()
if err != nil {
c.Close()
return nil, err
}
return ids, nil
}
func (c *Conn) ReceiveAddressAssignment(ctx context.Context) ([]AssignedAddress, error) {
select {
case <-ctx.Done():
return nil, ctx.Err()
case assignment := <-c.assignedAddressUpdates:
return assignment, nil
case <-c.closeChan:
select {
case assignment := <-c.assignedAddressUpdates:
return assignment, nil
default:
return nil, c.closeErr
}
}
}
func (c *Conn) ReceiveAddressRequest(ctx context.Context) (*AddressRequest, error) {
var requested *addressRequestCapsule
select {
case <-ctx.Done():
return nil, ctx.Err()
case requested = <-c.addressRequests:
case <-c.closeChan:
select {
case requested = <-c.addressRequests:
default:
return nil, c.closeErr
}
}
return newAddressRequest(c, requested), nil
}
func (c *Conn) AssignAddresses(prefixes []netip.Prefix) error {
capsule := &addressAssignCapsule{}
if prefixes != nil {
capsule.AssignedAddresses = make([]AssignedAddress, len(prefixes))
for i, p := range prefixes {
capsule.AssignedAddresses[i] = AssignedAddress{IPPrefix: p}
}
}
return c.sendAddressAssignment(capsule, true)
}
func (c *Conn) sendAddressAssignment(capsule *addressAssignCapsule, restrictPeer bool) error {
c.mu.Lock()
if c.closeErr != nil {
err := c.closeErr
c.mu.Unlock()
return err
}
if err := c.queueWrite(streamWrite{Data: capsule.append(nil)}); err != nil {
c.mu.Unlock()
c.Close()
return err
}
if !restrictPeer && c.peerAddresses == nil {
c.mu.Unlock()
return nil
}
var prefixes []netip.Prefix
if capsule.AssignedAddresses != nil {
prefixes = make([]netip.Prefix, 0, len(capsule.AssignedAddresses))
}
for _, assigned := range capsule.AssignedAddresses {
if !assigned.Rejected() {
prefixes = append(prefixes, assigned.IPPrefix)
}
}
c.peerAddresses = prefixes
c.mu.Unlock()
return nil
}
func (c *Conn) queueWrite(w streamWrite) error {
if len(c.queuedWrites) >= maxQueuedCapsules {
c.closeErr = &CloseError{Remote: false}
close(c.closeChan)
c.str.CancelRead(quic.StreamErrorCode(http3.ErrCodeExcessiveLoad))
c.str.CancelWrite(quic.StreamErrorCode(http3.ErrCodeExcessiveLoad))
close(c.writeNotify)
return goerrors.New("connect-ip: capsule queue full")
}
c.queuedWrites = append(c.queuedWrites, w)
c.notifyWriter()
return nil
}
func (c *Conn) queueFin() {
c.str.SetWriteDeadline(time.Now())
c.queuedWrites = append(c.queuedWrites, streamWrite{Fin: true})
c.notifyWriter()
}
func (c *Conn) notifyWriter() {
select {
case c.writeNotify <- struct{}{}:
default:
}
}
func queueLatest[T any](ch chan T, value T) {
for {
select {
case ch <- value:
return
case <-ch:
}
}
}
func (c *Conn) Routes(ctx context.Context) ([]IPRoute, error) {
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-c.closeChan:
return nil, c.closeErr
case routes := <-c.availableRouteUpdates:
return routes, nil
}
}
func (c *Conn) readFromStream() error {
p := http3.NewCapsuleParser(c.str)
for {
t, cr, err := p.Next()
if goerrors.Is(err, io.EOF) {
return nil
}
if err != nil {
return err
}
switch t {
case capsuleTypeAddressAssign:
capsule, err := parseAddressAssignCapsule(cr)
if err != nil {
return err
}
prefixes := make([]netip.Prefix, 0, len(capsule.AssignedAddresses))
for _, assigned := range capsule.AssignedAddresses {
if !assigned.Rejected() {
prefixes = append(prefixes, assigned.IPPrefix)
}
}
c.mu.Lock()
c.assignedAddresses = prefixes
c.mu.Unlock()
select {
case c.assignedAddressUpdates <- capsule.AssignedAddresses:
default:
return fmt.Errorf("%w: address assignment queue full", errCapsuleLimit)
}
case capsuleTypeAddressRequest:
capsule, err := parseAddressRequestCapsule(cr)
if err != nil {
return err
}
select {
case c.addressRequests <- capsule:
default:
return fmt.Errorf("%w: address request queue full", errCapsuleLimit)
}
case capsuleTypeRouteAdvertisement:
capsule, err := parseRouteAdvertisementCapsule(cr)
if err != nil {
return err
}
queueLatest(c.availableRouteUpdates, capsule.IPAddressRanges)
case capsuleTypeDatagram:
c.datagramCapsuleOnce.Do(func() {
errors.LogWarning(context.Background(), "connect-ip: dropping IP packets sent in DATAGRAM capsules, only QUIC DATAGRAM frames are supported")
})
if err := cr.Discard(); err != nil {
return err
}
default:
if err := cr.Discard(); err != nil {
return err
}
}
}
}
func (c *Conn) writeToStream() error {
for range c.writeNotify {
for {
c.mu.Lock()
if len(c.queuedWrites) == 0 {
c.mu.Unlock()
break
}
w := c.queuedWrites[0]
c.queuedWrites[0] = streamWrite{}
c.queuedWrites = c.queuedWrites[1:]
c.mu.Unlock()
if w.Fin {
return c.str.Close()
}
if _, err := c.str.Write(w.Data); err != nil {
return err
}
}
}
return c.closeErr
}
func (c *Conn) ReadPacket(b []byte) (int, error) {
for {
select {
case <-c.closeChan:
return 0, c.closeErr
default:
}
data, err := c.str.ReceiveDatagram(context.Background())
if err != nil {
select {
case <-c.closeChan:
return 0, c.closeErr
default:
return 0, err
}
}
contextID, n, err := quicvarint.Parse(data)
if err != nil {
errors.LogDebugInner(context.Background(), err, "dropping malformed datagram")
continue
}
if contextID != 0 {
continue
}
packet := data[n:]
if err := c.handleIncomingProxiedPacket(packet); err != nil {
errors.LogDebugInner(context.Background(), err, "dropping proxied packet")
continue
}
if len(packet) > len(b) {
return 0, io.ErrShortBuffer
}
return copy(b, packet), nil
}
}
func (c *Conn) handleIncomingProxiedPacket(data []byte) error {
if len(data) == 0 {
return goerrors.New("connect-ip: empty packet")
}
var src, dst netip.Addr
var ipProto uint8
switch v := ipVersion(data); v {
default:
return fmt.Errorf("connect-ip: unknown IP versions: %d", v)
case 4:
if len(data) < ipv4.HeaderLen {
return fmt.Errorf("connect-ip: malformed datagram: too short")
}
src = netip.AddrFrom4([4]byte(data[12:16]))
dst = netip.AddrFrom4([4]byte(data[16:20]))
ipProto = data[9]
case 6:
if len(data) < ipv6.HeaderLen {
return fmt.Errorf("connect-ip: malformed datagram: too short")
}
src = netip.AddrFrom16([16]byte(data[8:24]))
dst = netip.AddrFrom16([16]byte(data[24:40]))
ipProto = data[6]
}
c.mu.Lock()
assignedAddresses := c.assignedAddresses
localRoutes := c.localRoutes
peerAddresses := c.peerAddresses
c.mu.Unlock()
if peerAddresses != nil {
if !slices.ContainsFunc(peerAddresses, func(p netip.Prefix) bool { return p.Contains(src) }) {
return fmt.Errorf("connect-ip: datagram source address not allowed: %s", src)
}
}
var isAllowedDst bool
if len(assignedAddresses) > 0 {
isAllowedDst = slices.ContainsFunc(assignedAddresses, func(p netip.Prefix) bool { return p.Contains(dst) })
}
if !isAllowedDst {
isAllowedDst = slices.ContainsFunc(localRoutes, func(r IPRoute) bool {
if r.StartIP.Compare(dst) > 0 || dst.Compare(r.EndIP) > 0 {
return false
}
if (ipVersion(data) == 4 && ipProto == ipProtoICMP) || (ipVersion(data) == 6 && ipProto == ipProtoICMPv6) {
return true
}
return r.IPProtocol == 0 || r.IPProtocol == ipProto
})
}
if !isAllowedDst {
return fmt.Errorf("connect-ip: datagram destination address / protocol not allowed: %s (protocol: %d)", dst, ipProto)
}
return nil
}
func (c *Conn) WritePacket(b []byte) (icmp []byte, err error) {
select {
case <-c.closeChan:
return nil, c.closeErr
default:
}
data, err := c.composeDatagram(b)
if err != nil {
errors.LogDebugInner(context.Background(), err, "dropping proxied packet (", len(b), " bytes) that can't be proxied")
return nil, nil
}
if err := c.str.SendDatagram(data); err != nil {
if tooLarge, ok := goerrors.AsType[*quic.DatagramTooLargeError](err); ok {
icmpPacket, err := composeICMPTooLargePacket(b, int(tooLarge.MaxDatagramPayloadSize)-c.datagramOverhead())
if err != nil {
if goerrors.Is(err, ErrMTUTooSmall) {
return nil, err
}
errors.LogDebugInner(context.Background(), err, "failed to compose ICMP Packet Too Big")
}
return icmpPacket, nil
}
select {
case <-c.closeChan:
return nil, c.closeErr
default:
return nil, err
}
}
return nil, nil
}
func (c *Conn) composeDatagram(b []byte) ([]byte, error) {
if len(b) == 0 {
return nil, goerrors.New("connect-ip: empty packet")
}
switch v := ipVersion(b); v {
default:
return nil, fmt.Errorf("connect-ip: unknown IP versions: %d", v)
case 4:
if len(b) < ipv4.HeaderLen {
return nil, fmt.Errorf("connect-ip: IPv4 packet too short")
}
hdrLen := int(b[0]&0x0f) << 2
totalLen := int(binary.BigEndian.Uint16(b[2:4]))
if hdrLen < ipv4.HeaderLen || hdrLen > totalLen || totalLen > len(b) {
return nil, fmt.Errorf("connect-ip: malformed IPv4 header: header length %d, total length %d, packet length %d", hdrLen, totalLen, len(b))
}
ttl := b[8]
if ttl <= 1 {
return nil, fmt.Errorf("connect-ip: datagram TTL too small: %d", ttl)
}
b[8]--
binary.BigEndian.PutUint16(b[10:12], calculateIPv4Checksum(b[:hdrLen]))
case 6:
if len(b) < ipv6.HeaderLen {
return nil, fmt.Errorf("connect-ip: IPv6 packet too short")
}
hopLimit := b[7]
if hopLimit <= 1 {
return nil, fmt.Errorf("connect-ip: datagram Hop Limit too small: %d", hopLimit)
}
b[7]--
}
data := make([]byte, 0, len(contextIDZero)+len(b))
data = append(data, contextIDZero...)
data = append(data, b...)
return data, nil
}
func (c *Conn) datagramOverhead() int {
return quicvarint.Len(uint64(c.str.StreamID()/4)) + len(contextIDZero)
}
func (c *Conn) MaxPacketSize() int {
select {
case <-c.closeChan:
return 0
default:
}
err := c.str.SendDatagram(make([]byte, 1<<16))
tooLarge, ok := goerrors.AsType[*quic.DatagramTooLargeError](err)
if !ok {
return 0
}
return max(0, int(tooLarge.MaxDatagramPayloadSize)-c.datagramOverhead())
}
func (c *Conn) Close() error {
c.closeOnce.Do(func() {
c.mu.Lock()
if c.closeErr == nil {
c.closeErr = &CloseError{Remote: false}
close(c.closeChan)
c.queueFin()
}
c.mu.Unlock()
c.closeResult = <-c.writeDone
c.str.CancelRead(quic.StreamErrorCode(http3.ErrCodeNoError))
})
return c.closeResult
}
func ipVersion(b []byte) uint8 { return b[0] >> 4 }
@@ -1,706 +0,0 @@
/* SPDX-License-Identifier: MIT
*
* Copyright 2024 Marten Seemann
* Adapted from github.com/quic-go/connect-ip-go (commit a0c35fa).
*/
package connectip
import (
"bytes"
"context"
"encoding/binary"
"errors"
"io"
"net"
"net/netip"
"sync"
"testing"
"time"
"github.com/apernet/quic-go"
"github.com/apernet/quic-go/http3"
"github.com/apernet/quic-go/quicvarint"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
)
var ipv6Header = []byte{
0x60, 0x00, 0x00, 0x00,
0x00, 0x20, 59, 64,
0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x20, 0x01, 0x0d, 0xb8, 0x85, 0xa3, 0x08, 0xd3, 0x13, 0x19, 0x8a, 0x2e, 0x03, 0x70, 0x73, 0x48,
}
var (
testSrc4 = netip.MustParseAddr("192.0.2.1")
testDst4 = netip.MustParseAddr("198.51.100.1")
testSrc6 = netip.MustParseAddr("2001:db8::1")
testDst6 = netip.MustParseAddr("2001:db8:1::1")
)
func ipv4Packet(ttl, proto uint8, src, dst netip.Addr, options, payload []byte) []byte {
hdrLen := ipv4.HeaderLen + len(options)
b := make([]byte, hdrLen, hdrLen+len(payload))
b[0] = 4<<4 | byte(hdrLen>>2)
binary.BigEndian.PutUint16(b[2:4], uint16(hdrLen+len(payload)))
b[8] = ttl
b[9] = proto
copy(b[12:16], src.AsSlice())
copy(b[16:20], dst.AsSlice())
copy(b[ipv4.HeaderLen:], options)
return append(b, payload...)
}
func ipv6Packet(hopLimit, nextHeader uint8, src, dst netip.Addr, payload []byte) []byte {
b := make([]byte, ipv6.HeaderLen, ipv6.HeaderLen+len(payload))
b[0] = 6 << 4
binary.BigEndian.PutUint16(b[4:6], uint16(len(payload)))
b[6] = nextHeader
b[7] = hopLimit
copy(b[8:24], src.AsSlice())
copy(b[24:40], dst.AsSlice())
return append(b, payload...)
}
func ipv4ChecksumValid(header []byte) bool {
var sum uint32
for i := 0; i+1 < len(header); i += 2 {
sum += uint32(binary.BigEndian.Uint16(header[i:]))
}
for sum > 0xffff {
sum = sum&0xffff + sum>>16
}
return sum == 0xffff
}
type mockStream struct {
streamID quic.StreamID
reading []byte
toRead <-chan []byte
datagrams <-chan []byte
maxDatagramPayloadSize int
sendDatagramErr error
sent [][]byte
writeStarted chan struct{}
written chan<- []byte
readErr error
mu sync.Mutex
cancelWriteCodes []quic.StreamErrorCode
}
func (m *mockStream) cancelWriteCode() (quic.StreamErrorCode, bool) {
m.mu.Lock()
defer m.mu.Unlock()
if len(m.cancelWriteCodes) == 0 {
return 0, false
}
return m.cancelWriteCodes[0], true
}
var _ http3Stream = &mockStream{}
func (m *mockStream) StreamID() quic.StreamID { return m.streamID }
func (m *mockStream) Read(p []byte) (int, error) {
if len(m.reading) == 0 && m.readErr != nil {
return 0, m.readErr
}
if len(m.reading) == 0 {
m.reading = <-m.toRead
}
n := copy(p, m.reading)
m.reading = m.reading[n:]
return n, nil
}
func (m *mockStream) CancelRead(quic.StreamErrorCode) {}
func (m *mockStream) Write(p []byte) (int, error) {
if m.writeStarted != nil {
close(m.writeStarted)
m.writeStarted = nil
}
if m.written != nil {
m.written <- bytes.Clone(p)
}
return len(p), nil
}
func (m *mockStream) Close() error { return nil }
func (m *mockStream) CancelWrite(code quic.StreamErrorCode) {
m.mu.Lock()
defer m.mu.Unlock()
m.cancelWriteCodes = append(m.cancelWriteCodes, code)
}
func (m *mockStream) SetWriteDeadline(time.Time) error { return nil }
func (m *mockStream) SendDatagram(data []byte) error {
if m.sendDatagramErr != nil {
return m.sendDatagramErr
}
if size := quicvarint.Len(uint64(m.streamID/4)) + len(data); m.maxDatagramPayloadSize > 0 && size > m.maxDatagramPayloadSize {
return &quic.DatagramTooLargeError{MaxDatagramPayloadSize: int64(m.maxDatagramPayloadSize)}
}
m.sent = append(m.sent, bytes.Clone(data))
return nil
}
func (m *mockStream) ReceiveDatagram(ctx context.Context) ([]byte, error) {
select {
case <-ctx.Done():
return nil, ctx.Err()
case data, ok := <-m.datagrams:
if !ok {
return nil, io.EOF
}
return data, nil
}
}
func TestCapsuleWriteQueueLimit(t *testing.T) {
writes := make(chan []byte)
writeStarted := make(chan struct{})
conn := newProxiedConn(&mockStream{
writeStarted: writeStarted,
written: writes,
})
t.Cleanup(func() { conn.Close() })
require.NoError(t, conn.AssignAddresses(nil))
select {
case <-writeStarted:
case <-time.After(time.Second):
t.Fatal("capsule write did not start")
}
for range maxQueuedCapsules {
require.NoError(t, conn.AssignAddresses(nil))
}
go func() {
conn.Routes(context.Background())
for range maxQueuedCapsules + 1 {
<-writes
}
}()
require.ErrorContains(t, conn.AssignAddresses(nil), "capsule queue full")
require.ErrorIs(t, conn.AssignAddresses(nil), net.ErrClosed)
}
func TestCapsuleReceiveQueueLimit(t *testing.T) {
for _, name := range []string{"assignments", "requests"} {
t.Run(name, func(t *testing.T) {
var data []byte
for i := range maxQueuedCapsules + 1 {
if name == "assignments" {
data = (&addressAssignCapsule{}).append(data)
} else {
data = (&addressRequestCapsule{
RequestIDs: []AddressRequestID{AddressRequestID(i + 1)},
Prefixes: []netip.Prefix{netip.MustParsePrefix("192.0.2.1/32")},
}).append(data)
}
}
conn := newProxiedConn(&mockStream{reading: data})
t.Cleanup(func() { conn.Close() })
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
_, err := conn.Routes(ctx)
require.ErrorIs(t, err, net.ErrClosed)
})
}
}
func TestAbortErrorCode(t *testing.T) {
var overflow []byte
for range maxQueuedCapsules + 1 {
overflow = (&addressAssignCapsule{}).append(overflow)
}
misordered := (&routeAdvertisementCapsule{IPAddressRanges: []IPRoute{
{StartIP: netip.MustParseAddr("192.0.2.2"), EndIP: netip.MustParseAddr("192.0.2.1")},
}}).append(nil)
for _, c := range []struct {
name string
str *mockStream
code http3.ErrCode
}{
{"malformed capsule", &mockStream{reading: misordered}, http3.ErrCodeMessageError},
{"queue limit", &mockStream{reading: overflow}, http3.ErrCodeExcessiveLoad},
{"reset by peer", &mockStream{readErr: &quic.StreamError{ErrorCode: quic.StreamErrorCode(http3.ErrCodeNoError), Remote: true}}, http3.ErrCodeRequestCanceled},
{"reset by peer on a request stream", &mockStream{readErr: &http3.Error{ErrorCode: http3.ErrCodeNoError, Remote: true}}, http3.ErrCodeRequestCanceled},
} {
t.Run(c.name, func(t *testing.T) {
conn := newProxiedConn(c.str)
t.Cleanup(func() { conn.Close() })
require.Eventually(t, func() bool {
_, ok := c.str.cancelWriteCode()
return ok
}, time.Second, time.Millisecond)
code, _ := c.str.cancelWriteCode()
require.Equal(t, quic.StreamErrorCode(c.code), code)
})
}
}
func TestIncomingDatagrams(t *testing.T) {
t.Run("empty packets", func(t *testing.T) {
conn := newProxiedConn(&mockStream{})
require.ErrorContains(t,
conn.handleIncomingProxiedPacket([]byte{}),
"connect-ip: empty packet",
)
})
t.Run("invalid IP version", func(t *testing.T) {
conn := newProxiedConn(&mockStream{})
data := make([]byte, 20)
data[0] = 5 << 4
require.ErrorContains(t,
conn.handleIncomingProxiedPacket(data),
"connect-ip: unknown IP versions: 5",
)
})
t.Run("IPv4 packet too short", func(t *testing.T) {
conn := newProxiedConn(&mockStream{})
data, err := (&ipv4.Header{
Src: net.IPv4(1, 2, 3, 4),
Dst: net.IPv4(159, 70, 42, 98),
Len: 20,
Checksum: 89,
}).Marshal()
require.NoError(t, err)
require.ErrorContains(t,
conn.handleIncomingProxiedPacket(data[:ipv4.HeaderLen-1]),
"connect-ip: malformed datagram: too short",
)
})
t.Run("IPv6 packet too short", func(t *testing.T) {
conn := newProxiedConn(&mockStream{})
require.ErrorContains(t,
conn.handleIncomingProxiedPacket(ipv6Header[:ipv6.HeaderLen-1]),
"connect-ip: malformed datagram: too short",
)
})
t.Run("invalid source address", func(t *testing.T) {
conn := newProxiedConn(&mockStream{})
require.NoError(t, conn.AssignAddresses([]netip.Prefix{netip.MustParsePrefix("192.168.0.10/32")}))
hdr := &ipv4.Header{
Src: net.IPv4(192, 168, 0, 11),
Dst: net.IPv4(159, 70, 42, 98),
Len: 20,
Checksum: 89,
}
data, err := hdr.Marshal()
require.NoError(t, err)
require.ErrorContains(t,
conn.handleIncomingProxiedPacket(data),
"connect-ip: datagram source address not allowed: 192.168.0.11",
)
})
t.Run("invalid destination address", func(t *testing.T) {
conn := newProxiedConn(&mockStream{})
require.NoError(t, conn.AssignAddresses([]netip.Prefix{netip.MustParsePrefix("192.168.0.10/32")}))
require.NoError(t, conn.AdvertiseRoute([]IPRoute{
{StartIP: netip.MustParseAddr("10.0.0.0"), EndIP: netip.MustParseAddr("10.1.2.3")},
}))
hdr := &ipv4.Header{
Src: net.IPv4(192, 168, 0, 10),
Dst: net.IPv4(10, 1, 2, 3),
Len: 20,
Checksum: 89,
}
data, err := hdr.Marshal()
require.NoError(t, err)
require.NoError(t, conn.handleIncomingProxiedPacket(data))
hdr.Dst = net.IPv4(10, 1, 2, 4)
data, err = hdr.Marshal()
require.NoError(t, err)
require.ErrorContains(t,
conn.handleIncomingProxiedPacket(data),
"connect-ip: datagram destination address / protocol not allowed: 10.1.2.4 (protocol: 0)",
)
})
t.Run("invalid IP protocol", func(t *testing.T) {
conn := newProxiedConn(&mockStream{})
require.NoError(t, conn.AssignAddresses([]netip.Prefix{netip.MustParsePrefix("192.168.0.10/32")}))
require.NoError(t, conn.AdvertiseRoute([]IPRoute{
{StartIP: netip.MustParseAddr("10.0.0.0"), EndIP: netip.MustParseAddr("10.1.2.3"), IPProtocol: 42},
}))
hdr := &ipv4.Header{
Src: net.IPv4(192, 168, 0, 10),
Dst: net.IPv4(10, 1, 2, 3),
Len: 20,
Checksum: 89,
Protocol: 42,
}
data, err := hdr.Marshal()
require.NoError(t, err)
require.NoError(t, conn.handleIncomingProxiedPacket(data))
hdr.Protocol = 41
data, err = hdr.Marshal()
require.NoError(t, err)
require.ErrorContains(t,
conn.handleIncomingProxiedPacket(data),
"connect-ip: datagram destination address / protocol not allowed: 10.1.2.3 (protocol: 41)",
)
hdr.Protocol = ipProtoICMP
data, err = hdr.Marshal()
require.NoError(t, err)
require.NoError(t, conn.handleIncomingProxiedPacket(data))
})
t.Run("packet from assigned address", func(t *testing.T) {
readChan := make(chan []byte, 1)
conn := newProxiedConn(&mockStream{toRead: readChan})
hdr := &ipv4.Header{
Src: net.IPv4(159, 70, 42, 98),
Dst: net.IPv4(192, 168, 0, 10),
Len: 20,
Checksum: 89,
}
data, err := hdr.Marshal()
require.NoError(t, err)
require.Error(t, conn.handleIncomingProxiedPacket(data), "connect-ip: datagram destination address")
readChan <- (&addressAssignCapsule{
AssignedAddresses: []AssignedAddress{{IPPrefix: netip.MustParsePrefix("192.168.0.10/32")}},
}).append(nil)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
_, err = conn.ReceiveAddressAssignment(ctx)
require.NoError(t, err)
require.NoError(t, conn.handleIncomingProxiedPacket(data))
})
}
func TestSkipUnknownCapsule(t *testing.T) {
for _, typ := range []http3.CapsuleType{42, capsuleTypeDatagram} {
readChan := make(chan []byte, 1)
conn := newProxiedConn(&mockStream{toRead: readChan})
data := quicvarint.Append(nil, uint64(typ))
data = quicvarint.Append(data, 3)
data = append(data, "foo"...)
data = (&addressAssignCapsule{
AssignedAddresses: []AssignedAddress{{IPPrefix: netip.MustParsePrefix("192.168.0.10/32")}},
}).append(data)
readChan <- data
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
assigned, err := conn.ReceiveAddressAssignment(ctx)
cancel()
require.NoError(t, err, "capsule type %d", typ)
require.Equal(t, []AssignedAddress{{IPPrefix: netip.MustParsePrefix("192.168.0.10/32")}}, assigned)
conn.Close()
}
}
func FuzzIncomingDatagram(f *testing.F) {
conn := newProxiedConn(&mockStream{})
require.NoError(f, conn.AssignAddresses([]netip.Prefix{
netip.MustParsePrefix("192.168.0.0/16"),
netip.MustParsePrefix("2001:db8::0/64"),
}))
require.NoError(f, conn.AdvertiseRoute([]IPRoute{
{StartIP: netip.MustParseAddr("10.0.0.0"), EndIP: netip.MustParseAddr("10.1.2.3"), IPProtocol: 42},
{StartIP: netip.MustParseAddr("2001:db8:1::"), EndIP: netip.MustParseAddr("2001:db8:1::ffff"), IPProtocol: 42},
}))
ipv4Header, err := (&ipv4.Header{
Src: net.IPv4(1, 2, 3, 4),
Dst: net.IPv4(159, 70, 42, 98),
Len: 20,
Checksum: 89,
}).Marshal()
require.NoError(f, err)
f.Add(ipv4Header)
f.Add(ipv6Header)
f.Fuzz(func(t *testing.T, data []byte) {
conn.handleIncomingProxiedPacket(data)
})
}
func TestSendingDatagrams(t *testing.T) {
t.Run("invalid IP version", func(t *testing.T) {
conn := newProxiedConn(&mockStream{})
data := make([]byte, 20)
data[0] = 5 << 4
_, err := conn.composeDatagram(data)
require.ErrorContains(t, err, "connect-ip: unknown IP versions: 5")
})
t.Run("IPv4 packet too short", func(t *testing.T) {
conn := newProxiedConn(&mockStream{})
data, err := (&ipv4.Header{
Src: net.IPv4(1, 2, 3, 4),
Dst: net.IPv4(159, 70, 42, 98),
Len: 20,
Checksum: 89,
}).Marshal()
require.NoError(t, err)
_, err = conn.composeDatagram(data[:ipv4.HeaderLen-1])
require.ErrorContains(t, err, "connect-ip: IPv4 packet too short")
})
t.Run("IPv6 packet too short", func(t *testing.T) {
conn := newProxiedConn(&mockStream{})
_, err := conn.composeDatagram(ipv6Header[:ipv6.HeaderLen-1])
require.ErrorContains(t, err, "connect-ip: IPv6 packet too short")
})
}
func TestWritePacketDropsWithoutSending(t *testing.T) {
setIHL := func(b []byte, ihl byte) []byte { b[0] = 4<<4 | ihl; return b }
setTotalLen := func(b []byte, l uint16) []byte { binary.BigEndian.PutUint16(b[2:4], l); return b }
payload := make([]byte, 20)
for _, tc := range []struct {
name string
packet []byte
}{
{"nil", nil},
{"empty", []byte{}},
{"IPv4 TTL 1", ipv4Packet(1, 17, testSrc4, testDst4, nil, payload)},
{"IPv4 TTL 0", ipv4Packet(0, 17, testSrc4, testDst4, nil, payload)},
{"IPv6 Hop Limit 1", ipv6Packet(1, 17, testSrc6, testDst6, payload)},
{"IPv6 Hop Limit 0", ipv6Packet(0, 17, testSrc6, testDst6, payload)},
{"IPv4 IHL below 5", setIHL(ipv4Packet(64, 17, testSrc4, testDst4, nil, payload), 4)},
{"IPv4 IHL beyond total length", setIHL(ipv4Packet(64, 17, testSrc4, testDst4, nil, payload[:8]), 8)},
{"IPv4 IHL beyond packet", setTotalLen(setIHL(ipv4Packet(64, 17, testSrc4, testDst4, nil, payload), 15), 60)},
{"IPv4 total length beyond packet", setTotalLen(ipv4Packet(64, 17, testSrc4, testDst4, nil, payload), 41)},
{"IPv4 total length below header", setTotalLen(ipv4Packet(64, 17, testSrc4, testDst4, nil, payload), 19)},
} {
t.Run(tc.name, func(t *testing.T) {
str := &mockStream{}
conn := newProxiedConn(str)
t.Cleanup(func() { conn.Close() })
orig := bytes.Clone(tc.packet)
icmpPacket, err := conn.WritePacket(tc.packet)
require.NoError(t, err)
require.Nil(t, icmpPacket)
require.Empty(t, str.sent)
require.Equal(t, orig, tc.packet, "dropped packets must not be modified")
})
}
}
func TestWritePacketIPv4Checksum(t *testing.T) {
for _, tc := range []struct {
name string
options []byte
}{
{"no options", nil},
{"Router Alert option", []byte{0x94, 0x04, 0x00, 0x00}},
{"maximum header length", bytes.Repeat([]byte{0x01}, 40)},
} {
t.Run(tc.name, func(t *testing.T) {
str := &mockStream{}
conn := newProxiedConn(str)
t.Cleanup(func() { conn.Close() })
packet := ipv4Packet(64, 17, testSrc4, testDst4, tc.options, []byte("foobar"))
icmpPacket, err := conn.WritePacket(packet)
require.NoError(t, err)
require.Nil(t, icmpPacket)
require.Len(t, str.sent, 1)
require.Equal(t, contextIDZero, str.sent[0][:len(contextIDZero)])
sent := str.sent[0][len(contextIDZero):]
require.Len(t, sent, len(packet))
require.Equal(t, uint8(63), sent[8])
require.True(t, ipv4ChecksumValid(sent[:ipv4.HeaderLen+len(tc.options)]))
})
}
}
func TestWritePacketTooLarge(t *testing.T) {
for _, tc := range []struct {
name string
streamID quic.StreamID
maxPayloadSize int
ipv6 bool
wantMTU int
wantMTUTooSmall bool
}{
{name: "IPv4", maxPayloadSize: 1200, wantMTU: 1198},
{name: "IPv4, 2-byte Quarter Stream ID", streamID: 4 * 64, maxPayloadSize: 1200, wantMTU: 1197},
{name: "IPv4 minimum MTU", maxPayloadSize: 70, wantMTU: 68},
{name: "IPv4 below minimum MTU", maxPayloadSize: 69, wantMTU: 67, wantMTUTooSmall: true},
{name: "IPv6", maxPayloadSize: 1400, ipv6: true, wantMTU: 1398},
{name: "IPv6, 4-byte Quarter Stream ID", streamID: 4 * 20000, maxPayloadSize: 1400, ipv6: true, wantMTU: 1395},
{name: "IPv6 minimum MTU", maxPayloadSize: 1282, ipv6: true, wantMTU: 1280},
{name: "IPv6 below minimum MTU", maxPayloadSize: 1281, ipv6: true, wantMTU: 1279, wantMTUTooSmall: true},
} {
t.Run(tc.name, func(t *testing.T) {
str := &mockStream{streamID: tc.streamID, maxDatagramPayloadSize: tc.maxPayloadSize}
conn := newProxiedConn(str)
t.Cleanup(func() { conn.Close() })
packetOfSize := func(size int) []byte {
if tc.ipv6 {
return ipv6Packet(64, 17, testSrc6, testDst6, make([]byte, size-ipv6.HeaderLen))
}
return ipv4Packet(64, 17, testSrc4, testDst4, nil, make([]byte, size-ipv4.HeaderLen))
}
require.Equal(t, tc.wantMTU, conn.MaxPacketSize())
icmpPacket, err := conn.WritePacket(packetOfSize(tc.wantMTU))
require.NoError(t, err)
require.Nil(t, icmpPacket)
require.Len(t, str.sent, 1)
icmpPacket, err = conn.WritePacket(packetOfSize(tc.wantMTU + 1))
if tc.wantMTUTooSmall {
require.ErrorIs(t, err, ErrMTUTooSmall)
require.Nil(t, icmpPacket)
return
}
require.NoError(t, err)
if tc.ipv6 {
msg, err := icmp.ParseMessage(ipProtoICMPv6, icmpPacket[ipv6.HeaderLen:])
require.NoError(t, err)
require.Equal(t, ipv6.ICMPTypePacketTooBig, msg.Type)
require.Equal(t, tc.wantMTU, msg.Body.(*icmp.PacketTooBig).MTU)
} else {
msg := icmpPacket[ipv4.HeaderLen:]
require.Equal(t, []byte{3, 4}, msg[:2])
require.Equal(t, uint16(tc.wantMTU), binary.BigEndian.Uint16(msg[6:8]))
}
})
}
}
func TestMaxPacketSize(t *testing.T) {
t.Run("sends nothing", func(t *testing.T) {
str := &mockStream{streamID: 8, maxDatagramPayloadSize: 1350}
conn := newProxiedConn(str)
t.Cleanup(func() { conn.Close() })
require.Equal(t, 1348, conn.MaxPacketSize())
require.Empty(t, str.sent)
})
t.Run("datagrams unsupported", func(t *testing.T) {
conn := newProxiedConn(&mockStream{sendDatagramErr: errors.New("datagram support disabled")})
t.Cleanup(func() { conn.Close() })
require.Zero(t, conn.MaxPacketSize())
})
t.Run("closed", func(t *testing.T) {
conn := newProxiedConn(&mockStream{maxDatagramPayloadSize: 1350})
require.NoError(t, conn.Close())
require.Zero(t, conn.MaxPacketSize())
})
}
func TestReadPacketDropsMalformedDatagrams(t *testing.T) {
packet := ipv4Packet(64, 17, testSrc4, testDst4, nil, []byte("foobar"))
for _, tc := range []struct {
name string
datagram []byte
}{
{"empty", []byte{}},
{"truncated Context ID", []byte{0x40}},
{"unknown Context ID", append([]byte{0x02}, packet...)},
{"empty IP packet", []byte{0x00}},
{"invalid IP packet", []byte{0x00, 0x50}},
} {
t.Run(tc.name, func(t *testing.T) {
datagrams := make(chan []byte, 2)
conn := newProxiedConn(&mockStream{datagrams: datagrams})
t.Cleanup(func() { conn.Close() })
require.NoError(t, conn.AdvertiseRoute([]IPRoute{
{StartIP: netip.IPv4Unspecified(), EndIP: netip.MustParseAddr("255.255.255.255")},
}))
datagrams <- tc.datagram
datagrams <- append(bytes.Clone(contextIDZero), packet...)
b := make([]byte, 1500)
n, err := conn.ReadPacket(b)
require.NoError(t, err)
require.Equal(t, packet, b[:n])
})
}
}
func TestReadPacketShortBuffer(t *testing.T) {
datagrams := make(chan []byte, 3)
conn := newProxiedConn(&mockStream{datagrams: datagrams})
t.Cleanup(func() { conn.Close() })
require.NoError(t, conn.AdvertiseRoute([]IPRoute{
{StartIP: netip.IPv4Unspecified(), EndIP: netip.MustParseAddr("255.255.255.255")},
}))
packet := ipv4Packet(64, 17, testSrc4, testDst4, nil, []byte("foobar"))
for range 3 {
datagrams <- append(bytes.Clone(contextIDZero), packet...)
}
n, err := conn.ReadPacket(make([]byte, len(packet)-1))
require.ErrorIs(t, err, io.ErrShortBuffer)
require.Zero(t, n)
b := make([]byte, len(packet))
n, err = conn.ReadPacket(b)
require.NoError(t, err)
require.Equal(t, packet, b[:n])
n, err = conn.ReadPacket(make([]byte, 1500))
require.NoError(t, err)
require.Equal(t, len(packet), n)
}
func TestCloseConcurrently(t *testing.T) {
for _, side := range []string{"client", "proxy"} {
t.Run(side, func(t *testing.T) {
client, server := setupConns(t)
conn := client
if side == "proxy" {
conn = server
}
readErr := make(chan error, 1)
go func() {
b := make([]byte, 1500)
for {
if _, err := conn.ReadPacket(b); err != nil {
readErr <- err
return
}
}
}()
writeErr := make(chan error, 1)
go func() {
for {
if _, err := conn.WritePacket(ipv4Packet(64, 17, testSrc4, testDst4, nil, nil)); err != nil {
writeErr <- err
return
}
}
}()
var wg sync.WaitGroup
for range 4 {
wg.Go(func() { assert.NoError(t, conn.Close()) })
}
wg.Wait()
for _, errChan := range []chan error{readErr, writeErr} {
select {
case err := <-errChan:
require.ErrorIs(t, err, net.ErrClosed)
case <-time.After(5 * time.Second):
t.Fatal("timeout")
}
}
require.NoError(t, conn.Close())
})
}
}
@@ -1,94 +0,0 @@
/* SPDX-License-Identifier: MIT
*
* Copyright 2024 Marten Seemann
* Adapted from github.com/quic-go/connect-ip-go (commit a0c35fa).
*/
package connectip
import (
"encoding/binary"
"errors"
"fmt"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
)
const (
ipv4MinMTU = 68
ipv6MinMTU = 1280
)
var ErrMTUTooSmall = errors.New("connect-ip: tunnel MTU below the minimum link MTU")
func composeICMPTooLargePacket(b []byte, mtu int) ([]byte, error) {
if len(b) == 0 {
return nil, errors.New("connect-ip: empty packet")
}
var icmpMessage *icmp.Message
var psh []byte
switch v := ipVersion(b); v {
case 4:
if len(b) < ipv4.HeaderLen {
return nil, errors.New("connect-ip: IPv4 packet too short")
}
if mtu < ipv4MinMTU {
return nil, fmt.Errorf("%w: %d bytes", ErrMTUTooSmall, mtu)
}
icmpMessage = &icmp.Message{
Type: ipv4.ICMPTypeDestinationUnreachable,
Code: 4,
Body: &icmp.PacketTooBig{
MTU: mtu,
Data: b[:min(len(b), max(ipv4.HeaderLen, int(b[0]&0x0f)<<2)+8)],
},
}
case 6:
if len(b) < ipv6.HeaderLen {
return nil, errors.New("connect-ip: IPv6 packet too short")
}
if mtu < ipv6MinMTU {
return nil, fmt.Errorf("%w: %d bytes", ErrMTUTooSmall, mtu)
}
icmpMessage = &icmp.Message{
Type: ipv6.ICMPTypePacketTooBig,
Body: &icmp.PacketTooBig{
MTU: mtu,
Data: b[:min(len(b), 1232)],
},
}
psh = icmp.IPv6PseudoHeader(b[24:40], b[8:24])
default:
return nil, fmt.Errorf("connect-ip: unknown IP version: %d", v)
}
icmp, err := icmpMessage.Marshal(psh)
if err != nil {
return nil, fmt.Errorf("connect-ip: failed to marshal ICMP message: %w", err)
}
if ipVersion(b) == 4 {
var header [ipv4.HeaderLen]byte
header[0] = 4<<4 | ipv4.HeaderLen>>2
ipLen := ipv4.HeaderLen + len(icmp)
binary.BigEndian.PutUint16(header[2:4], uint16(ipLen))
header[8] = 64
header[9] = 1
copy(header[12:16], b[16:20])
copy(header[16:20], b[12:16])
binary.BigEndian.PutUint16(header[10:12], calculateIPv4Checksum(header[:]))
return append(header[:], icmp...), nil
}
var header [ipv6.HeaderLen]byte
header[0] = 6 << 4
binary.BigEndian.PutUint16(header[4:6], uint16(len(icmp)))
header[6] = 58
header[7] = 64
copy(header[8:24], b[24:40])
copy(header[24:40], b[8:24])
return append(header[:], icmp...), nil
}
@@ -1,158 +0,0 @@
/* SPDX-License-Identifier: MIT
*
* Copyright 2024 Marten Seemann
* Adapted from github.com/quic-go/connect-ip-go (commit a0c35fa).
*/
package connectip
import (
"encoding/binary"
"net"
"net/netip"
"testing"
"github.com/stretchr/testify/require"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
)
func TestICMPTooLargeIPv4(t *testing.T) {
src := netip.MustParseAddr("192.168.1.1")
dst := netip.MustParseAddr("8.8.8.8")
origHdr := &ipv4.Header{
Version: 4,
Len: ipv4.HeaderLen,
TotalLen: 60,
TTL: 64,
Protocol: 6,
Src: src.AsSlice(),
Dst: dst.AsSlice(),
}
origBytes, err := origHdr.Marshal()
require.NoError(t, err)
data, err := composeICMPTooLargePacket(origBytes, 1200)
require.NoError(t, err)
hdr, err := ipv4.ParseHeader(data)
require.NoError(t, err)
require.Equal(t, 4, hdr.Version)
require.Equal(t, ipProtoICMP, hdr.Protocol)
require.Equal(t, dst.String(), hdr.Src.String())
require.Equal(t, src.String(), hdr.Dst.String())
require.Equal(t, uint16(hdr.Checksum), calculateIPv4Checksum(data[:ipv4.HeaderLen]))
icmpMsg, err := icmp.ParseMessage(ipProtoICMP, data[ipv4.HeaderLen:])
require.NoError(t, err)
require.Equal(t, ipv4.ICMPTypeDestinationUnreachable, icmpMsg.Type)
require.Equal(t, 4, icmpMsg.Code)
require.Equal(t, uint16(1200), binary.BigEndian.Uint16(data[ipv4.HeaderLen+6:]))
require.Equal(t, origBytes, data[ipv4.HeaderLen+8:])
}
func TestICMPTooLargeIPv4Options(t *testing.T) {
options := []byte{0x94, 0x04, 0x00, 0x00}
orig := ipv4Packet(64, 6, netip.MustParseAddr("192.168.1.1"), netip.MustParseAddr("8.8.8.8"), options, make([]byte, 20))
data, err := composeICMPTooLargePacket(orig, 1200)
require.NoError(t, err)
require.Equal(t, orig[:ipv4.HeaderLen+len(options)+8], data[ipv4.HeaderLen+8:])
}
func TestICMPTooLargeIPv6(t *testing.T) {
const mtu = 1337
src := netip.MustParseAddr("2001:db8::1")
dst := netip.MustParseAddr("1:2:3:4::5")
orig := []byte{
0x60, 0x00, 0x00, 0x00,
0x00, 0x00,
0x00, 0x2a,
}
orig = append(orig, src.AsSlice()...)
orig = append(orig, dst.AsSlice()...)
orig = append(orig, []byte("foobar")...)
data, err := composeICMPTooLargePacket(orig, mtu)
require.NoError(t, err)
hdr, err := ipv6.ParseHeader(data)
require.NoError(t, err)
require.Equal(t, 6, hdr.Version)
require.Equal(t, ipProtoICMPv6, hdr.NextHeader)
require.Equal(t, dst.String(), hdr.Src.String())
require.Equal(t, src.String(), hdr.Dst.String())
icmpMsg, err := icmp.ParseMessage(ipProtoICMPv6, data[ipv6.HeaderLen:])
require.NoError(t, err)
require.Equal(t, ipv6.ICMPTypePacketTooBig, icmpMsg.Type)
icmpBody, ok := icmpMsg.Body.(*icmp.PacketTooBig)
require.True(t, ok)
require.Equal(t, mtu, icmpBody.MTU)
require.Equal(t, orig, icmpBody.Data)
}
func TestICMPTooLargeMinimumMTU(t *testing.T) {
ipv4Orig := ipv4Packet(64, 6, testSrc4, testDst4, nil, make([]byte, 100))
ipv6Orig := ipv6Packet(64, 6, testSrc6, testDst6, make([]byte, 1300))
for _, tc := range []struct {
name string
packet []byte
mtu int
tooSmall bool
}{
{"IPv4 minimum", ipv4Orig, 68, false},
{"IPv4 below minimum", ipv4Orig, 67, true},
{"IPv4 negative", ipv4Orig, -1, true},
{"IPv6 minimum", ipv6Orig, 1280, false},
{"IPv6 below minimum", ipv6Orig, 1279, true},
} {
t.Run(tc.name, func(t *testing.T) {
data, err := composeICMPTooLargePacket(tc.packet, tc.mtu)
if tc.tooSmall {
require.ErrorIs(t, err, ErrMTUTooSmall)
require.Nil(t, data)
return
}
require.NoError(t, err)
require.NotEmpty(t, data)
})
}
}
func TestICMPFailures(t *testing.T) {
t.Run("empty packet", func(t *testing.T) {
_, err := composeICMPTooLargePacket([]byte{}, 1)
require.EqualError(t, err, "connect-ip: empty packet")
})
t.Run("too short IPv4 header", func(t *testing.T) {
origHdr := &ipv4.Header{
Version: 4,
Len: ipv4.HeaderLen,
TotalLen: 60,
Src: net.IPv4(1, 2, 3, 4),
Dst: net.IPv4(5, 6, 7, 8),
}
data, err := origHdr.Marshal()
require.NoError(t, err)
_, err = composeICMPTooLargePacket(data[:ipv4.HeaderLen-1], 1)
require.EqualError(t, err, "connect-ip: IPv4 packet too short")
})
t.Run("too short IPv6 header", func(t *testing.T) {
data := []byte{
0x60, 0x00, 0x00, 0x00,
0x00, 0x00,
0x00, 0x40,
}
data = append(data, net.ParseIP("2001:db8::1").To16()...)
data = append(data, net.ParseIP("2001:db8::2").To16()...)
_, err := composeICMPTooLargePacket(data[:ipv6.HeaderLen-1], 1)
require.EqualError(t, err, "connect-ip: IPv6 packet too short")
})
t.Run("unknown IP version", func(t *testing.T) {
data := []byte{
0x30, 0x00, 0x00, 0x00,
}
_, err := composeICMPTooLargePacket(data, 1)
require.EqualError(t, err, "connect-ip: unknown IP version: 3")
})
}
@@ -1,73 +0,0 @@
/* SPDX-License-Identifier: MIT
*
* Copyright 2024 Marten Seemann
* Adapted from github.com/quic-go/connect-ip-go (commit a0c35fa).
*/
package connectip
import "net/netip"
func rangeToPrefixes(start, end netip.Addr) []netip.Prefix {
var prefixes []netip.Prefix
for current := start; current.Compare(end) <= 0; {
prefix := findLargestPrefix(current, end)
prefixes = append(prefixes, prefix)
lastIP := lastIPInPrefix(prefix)
if lastIP.Compare(end) >= 0 {
break
}
current = lastIP.Next()
}
return prefixes
}
func findLargestPrefix(start, end netip.Addr) netip.Prefix {
if start == end {
return netip.PrefixFrom(start, start.BitLen())
}
var prefixLen int
for prefixLen = start.BitLen(); prefixLen > 0; prefixLen-- {
prefix := netip.PrefixFrom(start, prefixLen-1)
if lastIPInPrefix(prefix).Compare(end) > 0 || !isAligned(start, prefixLen-1) {
break
}
}
return netip.PrefixFrom(start, prefixLen)
}
func lastIPInPrefix(prefix netip.Prefix) netip.Addr {
addr := prefix.Addr()
bits := addr.As16()
hostBits := addr.BitLen() - prefix.Bits()
for i := len(bits) - 1; i >= 0 && hostBits > 0; i-- {
bitsInThisByte := min(8, hostBits)
mask := byte((1 << bitsInThisByte) - 1)
bits[i] |= mask
hostBits -= bitsInThisByte
}
if addr.Is4() {
return netip.AddrFrom4([4]byte(bits[12:16]))
}
return netip.AddrFrom16(bits)
}
func isAligned(addr netip.Addr, prefixLen int) bool {
bits := addr.As16()
hostBits := addr.BitLen() - prefixLen
for i := len(bits) - 1; i >= 0 && hostBits > 0; i-- {
bitsInThisByte := min(8, hostBits)
mask := byte((1 << bitsInThisByte) - 1)
if bits[i]&mask != 0 {
return false
}
hostBits -= bitsInThisByte
}
return true
}
@@ -1,78 +0,0 @@
/* SPDX-License-Identifier: MIT
*
* Copyright 2024 Marten Seemann
* Adapted from github.com/quic-go/connect-ip-go (commit a0c35fa).
*/
package connectip
import (
"fmt"
"net/netip"
"testing"
"github.com/stretchr/testify/require"
)
func TestIPRanges(t *testing.T) {
tests := []struct {
start, end netip.Addr
want []netip.Prefix
}{
{
start: netip.MustParseAddr("192.168.1.1"),
end: netip.MustParseAddr("192.168.1.1"),
want: []netip.Prefix{netip.MustParsePrefix("192.168.1.1/32")},
},
{
start: netip.MustParseAddr("192.168.1.0"),
end: netip.MustParseAddr("192.168.1.1"),
want: []netip.Prefix{netip.MustParsePrefix("192.168.1.0/31")},
},
{
start: netip.MustParseAddr("192.168.1.1"),
end: netip.MustParseAddr("192.168.1.2"),
want: []netip.Prefix{netip.MustParsePrefix("192.168.1.1/32"), netip.MustParsePrefix("192.168.1.2/32")},
},
{
start: netip.MustParseAddr("192.168.1.0"),
end: netip.MustParseAddr("192.168.1.255"),
want: []netip.Prefix{netip.MustParsePrefix("192.168.1.0/24")},
},
{
start: netip.MustParseAddr("10.0.0.0"),
end: netip.MustParseAddr("10.1.0.255"),
want: []netip.Prefix{netip.MustParsePrefix("10.0.0.0/16"), netip.MustParsePrefix("10.1.0.0/24")},
},
{
start: netip.MustParseAddr("2001:0db8:85a3::8a2e:0370:7334"),
end: netip.MustParseAddr("2001:0db8:85a3::8a2e:0370:7334"),
want: []netip.Prefix{netip.MustParsePrefix("2001:0db8:85a3::8a2e:0370:7334/128")},
},
{
start: netip.MustParseAddr("2001:db8::0"),
end: netip.MustParseAddr("2001:db8::ffff:ffff:ffff:ffff"),
want: []netip.Prefix{netip.MustParsePrefix("2001:db8::/64")},
},
{
start: netip.MustParseAddr("2001:db8::1"),
end: netip.MustParseAddr("2001:db8::2"),
want: []netip.Prefix{netip.MustParsePrefix("2001:db8::1/128"), netip.MustParsePrefix("2001:db8::2/128")},
},
{
start: netip.MustParseAddr("2001:db8:1234:5678::"),
end: netip.MustParseAddr("2001:db8:1234:5679::"),
want: []netip.Prefix{
netip.MustParsePrefix("2001:db8:1234:5678::/64"),
netip.MustParsePrefix("2001:db8:1234:5679::/128"),
},
},
}
for _, test := range tests {
t.Run(fmt.Sprintf("%s-%s", test.start, test.end), func(t *testing.T) {
prefixes := rangeToPrefixes(test.start, test.end)
require.Equal(t, test.want, prefixes)
})
}
}
@@ -1,30 +0,0 @@
/* SPDX-License-Identifier: MIT
*
* Copyright 2024 Marten Seemann
* Adapted from github.com/quic-go/connect-ip-go (commit a0c35fa).
*/
package connectip
import (
"errors"
"net/http"
"github.com/apernet/quic-go/http3"
"github.com/apernet/quic-go/quicvarint"
)
var contextIDZero = quicvarint.Append([]byte{}, 0)
type Proxy struct{}
func (s *Proxy) Proxy(w http.ResponseWriter, _ *ProxyRequest) (*Conn, error) {
streamer, ok := w.(http3.HTTPStreamer)
if !ok {
return nil, errors.New("connect-ip: response writer is not an HTTP/3 stream")
}
w.Header().Set(http3.CapsuleProtocolHeader, capsuleProtocolHeaderValue)
w.WriteHeader(http.StatusOK)
return newProxiedConn(streamer.HTTPStream()), nil
}
@@ -1,413 +0,0 @@
/* SPDX-License-Identifier: MIT
*
* Copyright 2024 Marten Seemann
* Adapted from github.com/quic-go/connect-ip-go (commit a0c35fa).
*/
package connectip
import (
"context"
"crypto/tls"
"encoding/binary"
"fmt"
"net"
"net/http"
"net/netip"
"slices"
"testing"
"time"
"github.com/apernet/quic-go"
"github.com/apernet/quic-go/http3"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
)
func dialHTTP3(t *testing.T, addr string) *http3.ClientConn {
t.Helper()
ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
defer cancel()
qconn, err := quic.DialAddr(
ctx,
addr,
&tls.Config{ServerName: "localhost", RootCAs: certPool, NextProtos: []string{http3.NextProtoH3}},
&quic.Config{EnableDatagrams: true, InitialPacketSize: 1350, DisablePathMTUDiscovery: true},
)
require.NoError(t, err)
t.Cleanup(func() { qconn.CloseWithError(0, "") })
return (&http3.Transport{EnableDatagrams: true}).NewClientConn(qconn)
}
func setupConns(t *testing.T) (client, server *Conn) {
t.Helper()
p := &Proxy{}
conn, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0})
require.NoError(t, err)
t.Cleanup(func() { conn.Close() })
proxyURL := fmt.Sprintf("https://%s/connect-ip", conn.LocalAddr())
connChan := make(chan *Conn, 1)
mux := http.NewServeMux()
mux.HandleFunc("/connect-ip", func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "Bearer token", r.Header.Get("Authorization"))
mreq, err := ParseProxyRequest(r)
if !assert.NoError(t, err) {
w.WriteHeader(http.StatusBadRequest)
return
}
conn, err := p.Proxy(w, mreq)
if assert.NoError(t, err) {
connChan <- conn
}
})
s := http3.Server{
Handler: mux,
Addr: ":0",
EnableDatagrams: true,
TLSConfig: tlsConf,
}
go func() { s.Serve(conn) }()
t.Cleanup(func() { s.Close() })
ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
defer cancel()
req, err := NewRequest(ctx, proxyURL)
require.NoError(t, err)
req.Header().Set("Authorization", "Bearer token")
client, rsp, err := NewClientConn(dialHTTP3(t, conn.LocalAddr().String())).Dial(req)
require.NoError(t, err)
t.Cleanup(func() { client.Close() })
require.Equal(t, http.StatusOK, rsp.StatusCode)
select {
case <-time.After(5 * time.Second):
t.Fatal("timed out")
case server = <-connChan:
}
t.Cleanup(func() { server.Close() })
return client, server
}
func TestAddressAssignment(t *testing.T) {
client, server := setupConns(t)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
defer cancel()
_, err := server.ReceiveAddressAssignment(ctx)
require.ErrorIs(t, err, context.DeadlineExceeded)
ctx, cancel = context.WithTimeout(context.Background(), time.Second)
defer cancel()
pref1 := netip.MustParsePrefix("1.1.1.0/24")
pref2 := netip.MustParsePrefix("2001:db8::/64")
require.NoError(t, client.AssignAddresses([]netip.Prefix{pref1, pref2}))
assigned, err := server.ReceiveAddressAssignment(ctx)
require.NoError(t, err)
require.Equal(t, []AssignedAddress{{IPPrefix: pref1}, {IPPrefix: pref2}}, assigned)
require.NoError(t, client.AssignAddresses([]netip.Prefix{}))
assigned, err = server.ReceiveAddressAssignment(ctx)
require.NoError(t, err)
require.Empty(t, assigned)
}
func TestRejectingAddressRequestKeepsPeerUnrestricted(t *testing.T) {
client, server := setupConns(t)
ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
defer cancel()
clientAddr := netip.MustParsePrefix("192.0.2.2/32")
require.NoError(t, server.AssignAddresses([]netip.Prefix{clientAddr}))
_, err := client.ReceiveAddressAssignment(ctx)
require.NoError(t, err)
_, err = server.RequestAddresses([]netip.Prefix{netip.MustParsePrefix("0.0.0.0/32")})
require.NoError(t, err)
req, err := client.ReceiveAddressRequest(ctx)
require.NoError(t, err)
require.NoError(t, req.Respond([]netip.Prefix{{}}, nil))
assigned, err := server.ReceiveAddressAssignment(ctx)
require.NoError(t, err)
require.Len(t, assigned, 1)
require.True(t, assigned[0].Rejected())
packet := ipv4Packet(64, 17, netip.MustParseAddr("203.0.113.9"), clientAddr.Addr(), nil, []byte("foobar"))
_, err = server.WritePacket(slices.Clone(packet))
require.NoError(t, err)
received := make(chan []byte, 1)
go func() {
b := make([]byte, 1500)
if n, err := client.ReadPacket(b); err == nil {
received <- b[:n]
}
}()
select {
case b := <-received:
require.Equal(t, packet[20:], b[20:])
case <-ctx.Done():
t.Fatal("packet was not received")
}
}
func TestRejectingAddressRequestWithdrawsAssignment(t *testing.T) {
client, server := setupConns(t)
ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
defer cancel()
clientAddr := netip.MustParsePrefix("192.0.2.2/32")
dst := netip.MustParseAddr("198.51.100.1")
require.NoError(t, server.AssignAddresses([]netip.Prefix{clientAddr}))
require.NoError(t, server.AdvertiseRoute([]IPRoute{{StartIP: dst, EndIP: dst}}))
_, err := client.ReceiveAddressAssignment(ctx)
require.NoError(t, err)
received := make(chan []byte, 4)
go func() {
b := make([]byte, 1500)
for {
n, err := server.ReadPacket(b)
if err != nil {
return
}
received <- slices.Clone(b[20:n])
}
}()
send := func(payload string) {
_, err := client.WritePacket(ipv4Packet(64, 17, clientAddr.Addr(), dst, nil, []byte(payload)))
require.NoError(t, err)
}
send("assigned")
select {
case b := <-received:
require.Equal(t, "assigned", string(b))
case <-ctx.Done():
t.Fatal("packet from the assigned address was not received")
}
_, err = client.RequestAddresses([]netip.Prefix{netip.MustParsePrefix("0.0.0.0/32")})
require.NoError(t, err)
req, err := server.ReceiveAddressRequest(ctx)
require.NoError(t, err)
require.NoError(t, req.Respond([]netip.Prefix{{}}, nil))
assigned, err := client.ReceiveAddressAssignment(ctx)
require.NoError(t, err)
require.Len(t, assigned, 1)
require.True(t, assigned[0].Rejected())
send("withdrawn")
select {
case b := <-received:
t.Fatalf("packet from a withdrawn address was received: %q", b)
case <-time.After(200 * time.Millisecond):
}
}
func TestRouteAdvertisement(t *testing.T) {
client, server := setupConns(t)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
defer cancel()
_, err := server.Routes(ctx)
require.ErrorIs(t, err, context.DeadlineExceeded)
ctx, cancel = context.WithTimeout(context.Background(), time.Second)
defer cancel()
require.ErrorContains(t,
client.AdvertiseRoute([]IPRoute{
{StartIP: netip.MustParseAddr("1.1.1.2"), EndIP: netip.MustParseAddr("1.1.1.1"), IPProtocol: 42},
}),
"connect-ip: invalid route 0: start IP 1.1.1.2 is greater than end IP 1.1.1.1",
)
require.NoError(t, client.AdvertiseRoute([]IPRoute{
{StartIP: netip.MustParseAddr("1.1.1.1"), EndIP: netip.MustParseAddr("2.2.2.2"), IPProtocol: 42},
{StartIP: netip.MustParseAddr("2001:db8::1"), EndIP: netip.MustParseAddr("2001:db8::100"), IPProtocol: 24},
}))
routes, err := server.Routes(ctx)
require.NoError(t, err)
require.Equal(t, []IPRoute{
{StartIP: netip.MustParseAddr("1.1.1.1"), EndIP: netip.MustParseAddr("2.2.2.2"), IPProtocol: 42},
{StartIP: netip.MustParseAddr("2001:db8::1"), EndIP: netip.MustParseAddr("2001:db8::100"), IPProtocol: 24},
}, routes)
require.NoError(t, client.AdvertiseRoute([]IPRoute{}))
routes, err = server.Routes(ctx)
require.NoError(t, err)
require.Empty(t, routes)
}
func TestTTLs(t *testing.T) {
t.Run("IPv4", func(t *testing.T) {
client, server := setupConns(t)
require.NoError(t, server.AssignAddresses([]netip.Prefix{netip.MustParsePrefix("192.168.1.1/32")}))
require.NoError(t, server.AdvertiseRoute([]IPRoute{
{StartIP: netip.MustParseAddr("0.0.0.0"), EndIP: netip.MustParseAddr("255.255.255.255")},
}))
src, dst := netip.MustParseAddr("192.168.1.1"), netip.MustParseAddr("8.8.8.8")
icmp, err := client.WritePacket(ipv4Packet(1, 0, src, dst, nil, nil))
require.NoError(t, err)
require.Empty(t, icmp)
icmp, err = client.WritePacket(ipv4Packet(42, 0, src, dst, nil, nil))
require.NoError(t, err)
require.Empty(t, icmp)
receivedPacket := make([]byte, 1500)
n, err := server.ReadPacket(receivedPacket)
require.NoError(t, err)
receivedPacket = receivedPacket[:n]
receivedHdr, err := ipv4.ParseHeader(receivedPacket)
require.NoError(t, err)
require.Equal(t, uint16(receivedHdr.Checksum), calculateIPv4Checksum(receivedPacket[:ipv4.HeaderLen]))
require.Equal(t, 41, receivedHdr.TTL)
})
t.Run("IPv6", func(t *testing.T) {
client, server := setupConns(t)
require.NoError(t, server.AssignAddresses([]netip.Prefix{netip.MustParsePrefix("2001:db8::1/128")}))
require.NoError(t, server.AdvertiseRoute([]IPRoute{
{StartIP: netip.MustParseAddr("::"), EndIP: netip.MustParseAddr("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")},
}))
packetHopLimit1 := []byte{
0x60, 0x00, 0x00, 0x00,
0x00, 0x00,
0x00, 0x01,
0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x20, 0x01, 0x48, 0x60, 0x48, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x88,
}
icmp, err := client.WritePacket(packetHopLimit1)
require.NoError(t, err)
require.Empty(t, icmp)
packet := []byte{
0x60, 0x00, 0x00, 0x00,
0x00, 0x00,
0x00, 0x2A,
0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x20, 0x01, 0x48, 0x60, 0x48, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x88,
}
icmp, err = client.WritePacket(packet)
require.NoError(t, err)
require.Empty(t, icmp)
receivedPacket := make([]byte, 1500)
n, err := server.ReadPacket(receivedPacket)
require.NoError(t, err)
receivedPacket = receivedPacket[:n]
receivedHdr, err := ipv6.ParseHeader(receivedPacket)
require.NoError(t, err)
require.Equal(t, 41, receivedHdr.HopLimit)
})
}
func TestMaxPacketSizeOverQUIC(t *testing.T) {
client, server := setupConns(t)
require.NoError(t, server.AdvertiseRoute([]IPRoute{
{StartIP: netip.MustParseAddr("0.0.0.0"), EndIP: netip.MustParseAddr("255.255.255.255")},
}))
size := client.MaxPacketSize()
require.Greater(t, size, 1200)
require.Less(t, size, 1350)
icmp, err := client.WritePacket(ipv4Packet(64, 17, testSrc4, testDst4, nil, make([]byte, size-ipv4.HeaderLen)))
require.NoError(t, err)
require.Nil(t, icmp)
type readResult struct {
n int
err error
}
received := make(chan readResult, 1)
go func() {
n, err := server.ReadPacket(make([]byte, 1500))
received <- readResult{n, err}
}()
select {
case r := <-received:
require.NoError(t, r.err)
require.Equal(t, size, r.n)
case <-time.After(5 * time.Second):
t.Fatal("timeout")
}
icmp, err = client.WritePacket(ipv4Packet(64, 17, testSrc4, testDst4, nil, make([]byte, size+1-ipv4.HeaderLen)))
require.NoError(t, err)
require.NotNil(t, icmp)
require.Equal(t, uint16(size), binary.BigEndian.Uint16(icmp[ipv4.HeaderLen+6:]))
}
func TestClosing(t *testing.T) {
ipv6Packet := []byte{
0x60, 0x00, 0x00, 0x00,
0x00, 0x00,
0x00, 0x2A,
0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x20, 0x01, 0x48, 0x60, 0x48, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x88,
}
client, server := setupConns(t)
routeErrChan := make(chan error, 1)
prefixErrChan := make(chan error, 1)
go func() {
_, err := server.Routes(context.Background())
routeErrChan <- err
}()
go func() {
_, err := server.ReceiveAddressAssignment(context.Background())
prefixErrChan <- err
}()
require.NoError(t, client.Close())
_, err := client.ReceiveAddressAssignment(context.Background())
require.ErrorIs(t, err, net.ErrClosed)
var closeErr *CloseError
require.ErrorAs(t, err, &closeErr)
require.False(t, closeErr.Remote)
_, err = client.Routes(context.Background())
require.ErrorIs(t, err, net.ErrClosed)
require.ErrorIs(t,
client.AssignAddresses([]netip.Prefix{netip.MustParsePrefix("1.1.1.0/24")}),
net.ErrClosed,
)
require.ErrorIs(t,
client.AdvertiseRoute([]IPRoute{
{StartIP: netip.MustParseAddr("1.1.1.0"), EndIP: netip.MustParseAddr("1.1.1.1"), IPProtocol: 42},
}),
net.ErrClosed,
)
_, err = client.ReadPacket([]byte{0})
require.ErrorIs(t, err, net.ErrClosed)
_, err = client.WritePacket(ipv6Packet)
require.ErrorIs(t, err, net.ErrClosed)
select {
case err := <-routeErrChan:
require.ErrorIs(t, err, net.ErrClosed)
case <-time.After(time.Second):
t.Fatal("timeout")
}
select {
case err := <-prefixErrChan:
require.ErrorIs(t, err, net.ErrClosed)
case <-time.After(time.Second):
t.Fatal("timeout")
}
_, err = server.ReadPacket([]byte{0})
require.ErrorIs(t, err, net.ErrClosed)
_, err = server.WritePacket(ipv6Packet)
require.ErrorIs(t, err, net.ErrClosed)
}
@@ -1,91 +0,0 @@
/* SPDX-License-Identifier: MIT
*
* Copyright 2024 Marten Seemann
* Adapted from github.com/quic-go/connect-ip-go (commit a0c35fa).
*/
package connectip
import (
"context"
"errors"
"fmt"
"net/http"
"strings"
"github.com/apernet/quic-go/http3"
)
const requestProtocol = "connect-ip"
const capsuleProtocolHeaderValue = "?1"
type Request struct {
req *http.Request
}
func NewRequest(ctx context.Context, rawURL string) (*Request, error) {
if strings.ContainsAny(rawURL, "{}") {
return nil, errors.New("connect-ip: IP flow forwarding not supported: URL contains a URI Template expression")
}
req, err := http.NewRequestWithContext(ctx, http.MethodConnect, rawURL, nil)
if err != nil {
return nil, fmt.Errorf("connect-ip: failed to create request: %w", err)
}
if req.URL.Scheme != "https" || req.URL.Host == "" || !strings.HasPrefix(req.URL.Path, "/") {
return nil, fmt.Errorf("connect-ip: invalid proxy URL %q: expected an absolute https URL with a host and a path", rawURL)
}
req.Proto = requestProtocol
req.Host = req.URL.Host
req.Header.Set(http3.CapsuleProtocolHeader, capsuleProtocolHeaderValue)
return &Request{req: req}, nil
}
func (r *Request) Header() http.Header { return r.req.Header }
func (r *Request) httpRequest() *http.Request { return r.req }
type ProxyRequest struct{}
type ProxyRequestParseError struct {
HTTPStatus int
Err error
}
func (e *ProxyRequestParseError) Error() string { return e.Err.Error() }
func (e *ProxyRequestParseError) Unwrap() error { return e.Err }
func ParseProxyRequest(r *http.Request) (*ProxyRequest, error) {
if r.Method != http.MethodConnect {
return nil, &ProxyRequestParseError{
HTTPStatus: http.StatusMethodNotAllowed,
Err: fmt.Errorf("expected CONNECT request, got %s", r.Method),
}
}
if r.Proto != requestProtocol {
return nil, &ProxyRequestParseError{
HTTPStatus: http.StatusNotImplemented,
Err: fmt.Errorf("unexpected protocol: %s", r.Proto),
}
}
capsuleHeaderValues, ok := r.Header[http3.CapsuleProtocolHeader]
if !ok {
return nil, &ProxyRequestParseError{
HTTPStatus: http.StatusBadRequest,
Err: fmt.Errorf("missing Capsule-Protocol header"),
}
}
if !isCapsuleProtocolEnabled(capsuleHeaderValues) {
return nil, &ProxyRequestParseError{
HTTPStatus: http.StatusBadRequest,
Err: fmt.Errorf("invalid capsule header value: %s", capsuleHeaderValues),
}
}
return &ProxyRequest{}, nil
}
func isCapsuleProtocolEnabled(values []string) bool {
v := strings.Trim(strings.Join(values, ","), " ")
return v == capsuleProtocolHeaderValue || strings.HasPrefix(v, capsuleProtocolHeaderValue+";")
}
@@ -1,119 +0,0 @@
/* SPDX-License-Identifier: MIT
*
* Copyright 2024 Marten Seemann
* Adapted from github.com/quic-go/connect-ip-go (commit a0c35fa).
*/
package connectip
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/apernet/quic-go/http3"
"github.com/stretchr/testify/require"
)
func newRequest(target string) *http.Request {
req := httptest.NewRequest(http.MethodGet, target, nil)
req.Method = http.MethodConnect
req.Proto = requestProtocol
req.Header.Add("Capsule-Protocol", capsuleProtocolHeaderValue)
return req
}
func TestNewRequest(t *testing.T) {
req, err := NewRequest(t.Context(), "https://localhost:1234/masque/ip")
require.NoError(t, err)
httpReq := req.httpRequest()
require.Equal(t, http.MethodConnect, httpReq.Method)
require.Equal(t, requestProtocol, httpReq.Proto)
require.Equal(t, "localhost:1234", httpReq.Host)
require.Equal(t, "?1", req.Header().Get(http3.CapsuleProtocolHeader))
req.Header().Set("Authorization", "Bearer token")
require.Equal(t, "Bearer token", httpReq.Header.Get("Authorization"))
}
func TestNewRequestInvalidURL(t *testing.T) {
for _, tc := range []struct {
name, url, err string
}{
{"template with variables", "https://localhost/.well-known/masque/ip/{target}/{ipproto}/", "IP flow forwarding not supported"},
{"template with query variables", "https://localhost/masque/ip{?target,ipproto}", "IP flow forwarding not supported"},
{"not https", "http://localhost/masque/ip", "expected an absolute https URL"},
{"no host", "https:///masque/ip", "expected an absolute https URL"},
{"no path", "https://localhost", "expected an absolute https URL"},
{"relative", "/masque/ip", "expected an absolute https URL"},
{"unparsable", "https://local\x7fhost/", "failed to create request"},
} {
t.Run(tc.name, func(t *testing.T) {
_, err := NewRequest(t.Context(), tc.url)
require.ErrorContains(t, err, tc.err)
})
}
}
func TestProxyRequestParsing(t *testing.T) {
t.Run("valid request", func(t *testing.T) {
req := newRequest("https://localhost:1234/masque/ip")
r, err := ParseProxyRequest(req)
require.NoError(t, err)
require.Equal(t, &ProxyRequest{}, r)
})
t.Run("wrong protocol", func(t *testing.T) {
req := newRequest("https://localhost:1234/masque")
req.Proto = "not-connect-ip"
_, err := ParseProxyRequest(req)
require.EqualError(t, err, "unexpected protocol: not-connect-ip")
require.Equal(t, http.StatusNotImplemented, err.(*ProxyRequestParseError).HTTPStatus)
})
t.Run("wrong request method", func(t *testing.T) {
req := newRequest("https://localhost:1234/masque")
req.Method = http.MethodHead
_, err := ParseProxyRequest(req)
require.EqualError(t, err, "expected CONNECT request, got HEAD")
require.Equal(t, http.StatusMethodNotAllowed, err.(*ProxyRequestParseError).HTTPStatus)
})
t.Run("missing Capsule-Protocol header", func(t *testing.T) {
req := newRequest("https://localhost:1234/masque")
req.Header.Del("Capsule-Protocol")
_, err := ParseProxyRequest(req)
require.EqualError(t, err, "missing Capsule-Protocol header")
require.Equal(t, http.StatusBadRequest, err.(*ProxyRequestParseError).HTTPStatus)
})
for _, tc := range []struct {
name string
values []string
valid bool
}{
{name: "true", values: []string{"?1"}, valid: true},
{name: "surrounding spaces", values: []string{" ?1 "}, valid: true},
{name: "parameters", values: []string{"?1;a;b=?0;c=\"x\""}, valid: true},
{name: "false", values: []string{"?0"}},
{name: "integer", values: []string{"1"}},
{name: "empty", values: []string{""}},
{name: "not a structured field", values: []string{"🤡"}},
{name: "longer token", values: []string{"?10"}},
{name: "space before parameters", values: []string{"?1 ;a"}},
{name: "list", values: []string{"?1, ?1"}},
{name: "multiple field lines", values: []string{"?1", "?1"}},
} {
t.Run("Capsule-Protocol header: "+tc.name, func(t *testing.T) {
req := newRequest("https://localhost:1234/masque")
req.Header[http3.CapsuleProtocolHeader] = tc.values
_, err := ParseProxyRequest(req)
if tc.valid {
require.NoError(t, err)
return
}
require.ErrorContains(t, err, "invalid capsule header value")
require.Equal(t, http.StatusBadRequest, err.(*ProxyRequestParseError).HTTPStatus)
})
}
}
-237
View File
@@ -1,237 +0,0 @@
package masque
import (
"context"
"net/netip"
"reflect"
"runtime"
"strings"
"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/common/net/cnc"
"github.com/xtls/xray-core/common/utils"
"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/masque/connectip"
"github.com/xtls/xray-core/transport/internet/stat"
"github.com/xtls/xray-core/transport/internet/tls"
)
const (
MinPacketSize = 1280
initialPacketSize = 1350
)
func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (stat.Connection, error) {
tlsConfig := tls.ConfigFromStreamSettings(streamSettings)
if tlsConfig == nil {
return nil, errors.New("tls config is nil")
}
config := streamSettings.ProtocolSettings.(*Config)
dest.Network = net.Network_UDP
gotlsConfig := tlsConfig.GetTLSConfig(tls.WithDestination(dest))
gotlsConfig.NextProtos = []string{http3.NextProtoH3}
quicParams := streamSettings.QuicParams
if quicParams == nil {
quicParams = &internet.QuicParams{
BbrProfile: string(bbr.ProfileStandard),
}
}
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: -1,
InitialPacketSize: initialPacketSize,
DisablePathMTUDiscovery: quicParams.DisablePathMtuDiscovery || (runtime.GOOS != "linux" && runtime.GOOS != "windows" && runtime.GOOS != "darwin"),
EnableDatagrams: true,
DisablePathManager: true,
}
if quicParams.MaxIdleTimeout == 0 {
quicConfig.MaxIdleTimeout = 30 * time.Second
}
if quicParams.KeepAlivePeriod == 0 {
quicConfig.KeepAlivePeriod = net.QuicgoH3KeepAlivePeriod
}
var pktConn net.PacketConn
var udpAddr net.Addr
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.(*finalmask.PacketConnWrapper).PacketConn
udpAddr = conn.RemoteAddr()
} 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 *internet.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:
panic(reflect.TypeOf(c))
}
}
tr := &quic.Transport{Conn: pktConn, DisableGSO: quicParams.DisableGSO}
qconn, err := tr.Dial(ctx, udpAddr, gotlsConfig, quicConfig)
if err != nil {
tr.Close()
pktConn.Close()
return nil, err
}
context.AfterFunc(qconn.Context(), func() { tr.Close(); pktConn.Close() })
switch quicParams.Congestion {
case "reno":
case "", "bbr", "brutal":
congestion.UseBBR(qconn, bbr.Profile(quicParams.BbrProfile))
case "force-brutal":
congestion.UseBrutal(qconn, quicParams.BrutalUp, quicParams.BrutalDisableLossCompensation)
default:
qconn.CloseWithError(quic.ApplicationErrorCode(http3.ErrCodeNoError), "")
return nil, errors.New("unknown congestion control: ", quicParams.Congestion)
}
conn, err := establish(ctx, qconn, config, authority(config, gotlsConfig.ServerName, dest.Port))
if err != nil {
qconn.CloseWithError(quic.ApplicationErrorCode(http3.ErrCodeNoError), "")
return nil, err
}
return conn, nil
}
func establish(ctx context.Context, qconn *quic.Conn, config *Config, host string) (*Conn, error) {
stop := context.AfterFunc(ctx, func() {
qconn.CloseWithError(quic.ApplicationErrorCode(http3.ErrCodeRequestCanceled), "")
})
defer stop()
req, err := connectip.NewRequest(ctx, "https://"+host+config.Path)
if err != nil {
return nil, err
}
header := req.Header()
for k, v := range config.Headers {
header.Set(k, v)
}
switch header.Get("User-Agent") {
case "":
header["User-Agent"] = nil
case "chrome":
header.Set("User-Agent", utils.ChromeUA)
case "firefox":
header.Set("User-Agent", utils.FirefoxUA)
case "safari":
header.Set("User-Agent", utils.SafariUA)
case "edge":
header.Set("User-Agent", utils.MSEdgeUA)
case "curl":
header.Set("User-Agent", utils.CurlUA)
case "golang":
header.Del("User-Agent")
}
cc := (&http3.Transport{EnableDatagrams: true, DisableCompression: true}).NewClientConn(qconn)
ipConn, _, err := connectip.NewClientConn(cc).Dial(req)
if err != nil {
if ctx.Err() != nil {
err = context.Cause(ctx)
}
return nil, errors.New("CONNECT-IP request failed").Base(err)
}
if n := ipConn.MaxPacketSize(); n < MinPacketSize {
ipConn.Close()
return nil, errors.New("the tunnel can only carry ", n, "-byte packets, less than ", MinPacketSize)
}
if _, err := ipConn.RequestAddresses([]netip.Prefix{
netip.PrefixFrom(netip.IPv4Unspecified(), 32),
netip.PrefixFrom(netip.IPv6Unspecified(), 128),
}); err != nil {
ipConn.Close()
return nil, err
}
var local []netip.Addr
for len(local) == 0 {
assigned, err := ipConn.ReceiveAddressAssignment(ctx)
if err != nil {
ipConn.Close()
return nil, errors.New("no address assigned").Base(err)
}
local = localAddrs(assigned)
}
if !stop() {
ipConn.Close()
return nil, errors.New("no address assigned").Base(context.Cause(ctx))
}
conn := &Conn{
ipConn: ipConn,
quicConn: qconn,
local: local,
}
go conn.serveAddressAssignments()
go conn.serveAddressRequests()
return conn, nil
}
func localAddrs(assigned []connectip.AssignedAddress) []netip.Addr {
var local []netip.Addr
var has4, has6 bool
for _, a := range assigned {
if a.Rejected() {
continue
}
addr := a.IPPrefix.Addr()
if a.IPPrefix.Bits() != addr.BitLen() {
addr = a.IPPrefix.Masked().Addr().Next()
}
if addr.Is4() && !has4 {
has4 = true
local = append(local, addr)
} else if addr.Is6() && !has6 {
has6 = true
local = append(local, addr)
}
}
return local
}
func authority(config *Config, serverName string, port net.Port) string {
if config.Host != "" {
return config.Host
}
host := strings.TrimSuffix(strings.TrimPrefix(serverName, "["), "]")
if port == 443 {
if addr, err := netip.ParseAddr(host); err == nil && addr.Is6() {
return "[" + host + "]"
}
return host
}
return net.JoinHostPort(host, port.String())
}
func init() {
common.Must(internet.RegisterTransportDialer(protocolName, Dial))
}
-60
View File
@@ -1,60 +0,0 @@
package masque
import (
"net/netip"
"slices"
"testing"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet/masque/connectip"
)
func TestAuthority(t *testing.T) {
for _, c := range []struct {
host, serverName string
port net.Port
want string
}{
{serverName: "example.com", port: 443, want: "example.com"},
{serverName: "example.com", port: 8443, want: "example.com:8443"},
{serverName: "127.0.0.1", port: 443, want: "127.0.0.1"},
{serverName: "[2001:db8::1]", port: 443, want: "[2001:db8::1]"},
{serverName: "[2001:db8::1]", port: 8443, want: "[2001:db8::1]:8443"},
{serverName: "2001:db8::1", port: 8443, want: "[2001:db8::1]:8443"},
{host: "proxy.example", serverName: "example.com", port: 8443, want: "proxy.example"},
} {
if got := authority(&Config{Host: c.host}, c.serverName, c.port); got != c.want {
t.Errorf("authority(%q, %q, %d) = %q, want %q", c.host, c.serverName, c.port, got, c.want)
}
}
}
func TestLocalAddrs(t *testing.T) {
assigned := func(prefixes ...string) []connectip.AssignedAddress {
var a []connectip.AssignedAddress
for _, p := range prefixes {
a = append(a, connectip.AssignedAddress{IPPrefix: netip.MustParsePrefix(p)})
}
return a
}
addrs := func(s ...string) []netip.Addr {
var a []netip.Addr
for _, v := range s {
a = append(a, netip.MustParseAddr(v))
}
return a
}
for _, c := range []struct {
assigned []connectip.AssignedAddress
want []netip.Addr
}{
{assigned("192.0.2.2/32", "2001:db8::2/128"), addrs("192.0.2.2", "2001:db8::2")},
{assigned("2001:db8::/64", "192.0.2.0/24", "198.51.100.7/32"), addrs("2001:db8::1", "192.0.2.1")},
{assigned("0.0.0.0/32", "2001:db8::2/128"), addrs("2001:db8::2")},
{assigned("0.0.0.0/32", "::/128"), nil},
} {
if got := localAddrs(c.assigned); !slices.Equal(got, c.want) {
t.Errorf("localAddrs(%v) = %v, want %v", c.assigned, got, c.want)
}
}
}
+23 -47
View File
@@ -1,12 +1,7 @@
package internet
import (
"context"
"reflect"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/net/cnc"
"github.com/xtls/xray-core/transport/internet/finalmask"
)
@@ -17,7 +12,8 @@ type MemoryStreamConfig struct {
ProtocolSettings interface{}
SecurityType string
SecuritySettings interface{}
FinalMask *finalmask.FinalMask
TcpmaskManager *finalmask.TcpmaskManager
UdpmaskManager *finalmask.UdpmaskManager
QuicParams *QuicParams
SocketSettings *SocketConfig
DownloadSettings *MemoryStreamConfig
@@ -55,53 +51,33 @@ func ToMemoryStreamConfig(s *StreamConfig) (*MemoryStreamConfig, error) {
mss.SecuritySettings = ess
}
var tcpMasks []finalmask.TCPMask
var udpMasks []finalmask.UDPMask
if s != nil {
for i := range s.Tcpmasks {
instance := common.Must2(s.Tcpmasks[i].GetInstance())
tcpMasks = append(tcpMasks, instance.(finalmask.TCPMask))
}
for i := range s.Udpmasks {
instance := common.Must2(s.Udpmasks[i].GetInstance())
udpMasks = append(udpMasks, instance.(finalmask.UDPMask))
if s != nil && len(s.Tcpmasks) > 0 {
var masks []finalmask.Tcpmask
for _, msg := range s.Tcpmasks {
instance, err := msg.GetInstance()
if err != nil {
return nil, err
}
masks = append(masks, instance.(finalmask.Tcpmask))
}
mss.TcpmaskManager = finalmask.NewTcpmaskManager(masks)
}
dialTCP := func(ctx context.Context, dest net.Destination) (net.Conn, error) {
return DialSystem(ctx, dest, mss.SocketSettings)
}
listen := func(ctx context.Context, addr net.Addr) (net.Listener, error) {
return ListenSystem(ctx, addr, mss.SocketSettings)
}
dialUDP := func(ctx context.Context, dest net.Destination) (net.PacketConn, net.Addr, error) {
conn, err := DialSystem(ctx, dest, mss.SocketSettings)
if err != nil {
return nil, nil, err
}
var newConn net.PacketConn
var udpAddr net.Addr
switch c := conn.(type) {
case *PacketConnWrapper:
newConn = c.PacketConn
udpAddr = conn.RemoteAddr()
case *cnc.Connection:
newConn = &FakePacketConn{Conn: c}
udpAddr = &net.UDPAddr{IP: []byte{0, 0, 0, 0}, Port: 0}
default:
panic(reflect.TypeOf(c))
}
return newConn, udpAddr, nil
}
listenPacket := func(ctx context.Context, addr net.Addr) (net.PacketConn, error) {
return ListenSystemPacket(ctx, addr, mss.SocketSettings)
}
mss.FinalMask = finalmask.NewFinalMask(tcpMasks, udpMasks, dialTCP, listen, dialUDP, listenPacket)
if s != nil && s.QuicParams != nil {
mss.QuicParams = s.QuicParams
}
if s != nil && len(s.Udpmasks) > 0 {
var masks []finalmask.Udpmask
for _, msg := range s.Udpmasks {
instance, err := msg.GetInstance()
if err != nil {
return nil, err
}
masks = append(masks, instance.(finalmask.Udpmask))
}
mss.UdpmaskManager = finalmask.NewUdpmaskManager(masks)
}
return mss, nil
}
+60 -23
View File
@@ -5,10 +5,11 @@ import (
gotls "crypto/tls"
"fmt"
"io"
"math/rand"
"net/http"
"net/http/httptrace"
"net/url"
"reflect"
reflect "reflect"
"runtime"
"strconv"
"sync"
@@ -25,9 +26,9 @@ import (
"github.com/xtls/xray-core/common/signal/done"
"github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/browser_dialer"
"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/hysteria/udphop"
"github.com/xtls/xray-core/transport/internet/reality"
"github.com/xtls/xray-core/transport/internet/stat"
"github.com/xtls/xray-core/transport/internet/tls"
@@ -117,17 +118,20 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea
transportConfig := streamSettings.ProtocolSettings.(*Config)
dialContext := func(ctxInner context.Context) (net.Conn, error) {
var conn net.Conn
var err error
if streamSettings.FinalMask != nil {
conn, err = streamSettings.FinalMask.DialTCP(ctxInner, dest)
} else {
conn, err = internet.DialSystem(ctxInner, dest, streamSettings.SocketSettings)
}
conn, err := internet.DialSystem(ctxInner, dest, streamSettings.SocketSettings)
if err != nil {
return nil, err
}
if streamSettings.TcpmaskManager != nil {
newConn, err := streamSettings.TcpmaskManager.WrapConnClient(conn)
if err != nil {
conn.Close()
return nil, errors.New("mask err").Base(err)
}
conn = newConn
}
if realityConfig != nil {
return reality.UClient(conn, realityConfig, ctxInner, dest)
}
@@ -158,6 +162,7 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea
if quicParams == nil {
quicParams = &internet.QuicParams{
BbrProfile: string(bbr.ProfileStandard),
UdpHop: &internet.UdpHop{},
}
}
@@ -193,30 +198,62 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea
QUICConfig: quicConfig,
TLSClientConfig: gotlsConfig,
Dial: func(ctx context.Context, addr string, tlsCfg *gotls.Config, cfg *quic.Config) (*quic.Conn, error) {
var pktConn net.PacketConn
var udpAddr net.Addr
if streamSettings.FinalMask != nil {
conn, err := streamSettings.FinalMask.DialUDP(ctx, dest)
udpHopDialer := func(addr *net.UDPAddr) (net.PacketConn, error) {
conn, err := internet.DialSystem(ctx, net.UDPDestination(net.IPAddress(addr.IP), net.Port(addr.Port)), streamSettings.SocketSettings)
if err != nil {
return nil, errors.New("failed to dial to dest").Base(err)
}
pktConn = conn.(*finalmask.PacketConnWrapper).PacketConn
udpAddr = conn.RemoteAddr()
} else {
conn, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings)
if err != nil {
return nil, errors.New("failed to dial to dest").Base(err)
errors.LogInfoInner(context.Background(), err, "skip hop: failed to dial to dest")
return nil, errors.New("")
}
var pktConn net.PacketConn
switch c := conn.(type) {
case *internet.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:
panic(reflect.TypeOf(c))
}
return pktConn, nil
}
var pktConn net.PacketConn
var udpAddr *net.UDPAddr
var index int
if len(quicParams.UdpHop.Ports) > 0 {
index = rand.Intn(len(quicParams.UdpHop.Ports))
dest.Port = net.Port(quicParams.UdpHop.Ports[index])
}
raw, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings)
if err != nil {
return nil, errors.New("failed to dial to dest").Base(err)
}
switch c := raw.(type) {
case *internet.PacketConnWrapper:
pktConn = c.PacketConn
udpAddr = raw.RemoteAddr().(*net.UDPAddr)
case *cnc.Connection:
pktConn = &internet.FakePacketConn{Conn: c}
udpAddr = &net.UDPAddr{IP: c.RemoteAddr().(*net.TCPAddr).IP, Port: c.RemoteAddr().(*net.TCPAddr).Port}
default:
panic(reflect.TypeOf(c))
}
if len(quicParams.UdpHop.Ports) > 0 {
pktConn = udphop.NewUDPHopPacketConn(udphop.ToAddrs(udpAddr.IP, quicParams.UdpHop.Ports), time.Duration(quicParams.UdpHop.IntervalMin)*time.Second, time.Duration(quicParams.UdpHop.IntervalMax)*time.Second, udpHopDialer, pktConn, index)
}
if streamSettings.UdpmaskManager != nil {
newConn, err := streamSettings.UdpmaskManager.WrapPacketConnClient(pktConn)
if err != nil {
pktConn.Close()
return nil, errors.New("mask err").Base(err)
}
pktConn = newConn
}
tr := &quic.Transport{Conn: pktConn, DisableGSO: quicParams.DisableGSO}
+35 -23
View File
@@ -463,22 +463,37 @@ func ListenXH(ctx context.Context, address net.Address, port net.Port, streamSet
l.isH3 = len(tlsConfig.NextProtos) == 1 && tlsConfig.NextProtos[0] == "h3"
var err error
if l.isH3 {
var pktConn net.PacketConn
var err error
if streamSettings.FinalMask != nil {
pktConn, err = streamSettings.FinalMask.ListenPacket(context.Background(), &net.UDPAddr{IP: address.IP(), Port: int(port)})
} else {
pktConn, err = internet.ListenSystemPacket(context.Background(), &net.UDPAddr{IP: address.IP(), Port: int(port)}, streamSettings.SocketSettings)
if port == net.Port(0) { // unix
l.listener, err = internet.ListenSystem(ctx, &net.UnixAddr{
Name: address.Domain(),
Net: "unix",
}, streamSettings.SocketSettings)
if err != nil {
return nil, errors.New("failed to listen UNIX domain socket for XHTTP on ", address).Base(err)
}
errors.LogInfo(ctx, "listening UNIX domain socket for XHTTP on ", address)
} else if l.isH3 { // quic
Conn, err := internet.ListenSystemPacket(context.Background(), &net.UDPAddr{
IP: address.IP(),
Port: int(port),
}, streamSettings.SocketSettings)
if err != nil {
return nil, errors.New("failed to listen UDP for XHTTP/3 on ", address, ":", port).Base(err)
}
if streamSettings.UdpmaskManager != nil {
newConn, err := streamSettings.UdpmaskManager.WrapPacketConnServer(Conn)
if err != nil {
Conn.Close()
return nil, errors.New("mask err").Base(err)
}
Conn = newConn
}
quicParams := streamSettings.QuicParams
if quicParams == nil {
quicParams = &internet.QuicParams{
BbrProfile: string(bbr.ProfileStandard),
UdpHop: &internet.UdpHop{},
}
}
@@ -498,7 +513,7 @@ func ListenXH(ctx context.Context, address net.Address, port net.Port, streamSet
common.Must2(rand.Read((*k)[:]))
}
tr := &quic.Transport{Conn: pktConn, DisableGSO: quicParams.DisableGSO, StatelessResetKey: k}
tr := &quic.Transport{Conn: Conn, DisableGSO: quicParams.DisableGSO, StatelessResetKey: k}
l.h3listener, err = tr.ListenEarly(tlsConfig, quicConfig)
if err != nil {
@@ -520,24 +535,21 @@ func ListenXH(ctx context.Context, address net.Address, port net.Port, streamSet
errors.LogErrorInner(ctx, err, "failed to serve HTTP/3 for XHTTP/3")
}
_ = tr.Close()
_ = pktConn.Close()
_ = Conn.Close()
}()
} else {
var addr net.Addr
if port == net.Port(0) { // unix
addr = &net.UnixAddr{Name: address.Domain(), Net: "unix"}
} else { // tcp
addr = &net.TCPAddr{IP: address.IP(), Port: int(port)}
}
if streamSettings.FinalMask != nil {
l.listener, err = streamSettings.FinalMask.Listen(ctx, addr)
} else {
l.listener, err = internet.ListenSystem(ctx, addr, streamSettings.SocketSettings)
}
} else { // tcp
l.listener, err = internet.ListenSystem(ctx, &net.TCPAddr{
IP: address.IP(),
Port: int(port),
}, streamSettings.SocketSettings)
if err != nil {
return nil, errors.New("failed to listen ", addr.Network(), " for XHTTP on ", address, ":", port).Base(err)
return nil, errors.New("failed to listen TCP for XHTTP on ", address, ":", port).Base(err)
}
errors.LogInfo(ctx, "listening ", addr.Network(), " for XHTTP on ", address, ":", port)
errors.LogInfo(ctx, "listening TCP for XHTTP on ", address, ":", port)
}
if !l.isH3 && streamSettings.TcpmaskManager != nil {
l.listener, _ = streamSettings.TcpmaskManager.WrapListener(l.listener)
}
// tcp/unix (h1/h2)
+1 -1
View File
@@ -235,5 +235,5 @@ func (c *FakePacketConn) WriteTo(p []byte, _ net.Addr) (n int, err error) {
}
func (c *FakePacketConn) LocalAddr() net.Addr {
return &net.UDPAddr{IP: []byte{0, 0, 0, 0}}
return &net.UDPAddr{IP: c.Conn.LocalAddr().(*net.TCPAddr).IP, Port: c.Conn.LocalAddr().(*net.TCPAddr).Port}
}
+11 -8
View File
@@ -19,15 +19,18 @@ import (
// Dial dials a new TCP connection to the given destination.
func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (stat.Connection, error) {
errors.LogInfo(ctx, "dialing TCP to ", dest)
var conn net.Conn
var err error
if streamSettings.FinalMask != nil {
conn, err = streamSettings.FinalMask.DialTCP(ctx, dest)
} else {
conn, err = internet.DialSystem(ctx, dest, streamSettings.SocketSettings)
}
conn, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings)
if err != nil {
return nil, errors.New("failed to dial to dest").Base(err)
return nil, err
}
if streamSettings.TcpmaskManager != nil {
newConn, err := streamSettings.TcpmaskManager.WrapConnClient(conn)
if err != nil {
conn.Close()
return nil, errors.New("mask err").Base(err)
}
conn = newConn
}
if config := tls.ConfigFromStreamSettings(streamSettings); config != nil {
+19 -11
View File
@@ -41,21 +41,29 @@ func ListenTCP(ctx context.Context, address net.Address, port net.Port, streamSe
}
var listener net.Listener
var err error
var addr net.Addr
if port == net.Port(0) { // unix
addr = &net.UnixAddr{Name: address.Domain(), Net: "unix"}
} else { // tcp
addr = &net.TCPAddr{IP: address.IP(), Port: int(port)}
}
if streamSettings.FinalMask != nil {
listener, err = streamSettings.FinalMask.Listen(ctx, addr)
listener, err = internet.ListenSystem(ctx, &net.UnixAddr{
Name: address.Domain(),
Net: "unix",
}, streamSettings.SocketSettings)
if err != nil {
return nil, errors.New("failed to listen Unix Domain Socket on ", address).Base(err)
}
errors.LogInfo(ctx, "listening Unix Domain Socket on ", address)
} else {
listener, err = internet.ListenSystem(ctx, addr, streamSettings.SocketSettings)
listener, err = internet.ListenSystem(ctx, &net.TCPAddr{
IP: address.IP(),
Port: int(port),
}, streamSettings.SocketSettings)
if err != nil {
return nil, errors.New("failed to listen TCP on ", address, ":", port).Base(err)
}
errors.LogInfo(ctx, "listening TCP on ", address, ":", port)
}
if err != nil {
return nil, errors.New("failed to listen ", addr.Network(), " on ", address, ":", port).Base(err)
if streamSettings.TcpmaskManager != nil {
listener, _ = streamSettings.TcpmaskManager.WrapListener(listener)
}
errors.LogInfo(ctx, "listening ", addr.Network(), " on ", address, ":", port)
if streamSettings.SocketSettings != nil && streamSettings.SocketSettings.AcceptProxyProtocol {
errors.LogWarning(ctx, "accepting PROXY protocol")
+37 -8
View File
@@ -2,9 +2,12 @@ package udp
import (
"context"
"reflect"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/net/cnc"
"github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/stat"
)
@@ -12,14 +15,40 @@ import (
func init() {
common.Must(internet.RegisterTransportDialer(protocolName,
func(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (stat.Connection, error) {
if streamSettings != nil && streamSettings.FinalMask != nil {
return streamSettings.FinalMask.DialUDP(ctx, dest)
} else {
var sockopt *internet.SocketConfig
if streamSettings != nil && streamSettings.SocketSettings != nil {
sockopt = streamSettings.SocketSettings
}
return internet.DialSystem(ctx, dest, sockopt)
var sockopt *internet.SocketConfig
if streamSettings != nil {
sockopt = streamSettings.SocketSettings
}
conn, err := internet.DialSystem(ctx, dest, sockopt)
if err != nil {
return nil, err
}
if streamSettings != nil && streamSettings.UdpmaskManager != nil {
var pktConn net.PacketConn
var udpAddr *net.UDPAddr
switch c := conn.(type) {
case *internet.PacketConnWrapper:
pktConn = c.PacketConn
udpAddr = c.RemoteAddr().(*net.UDPAddr)
case *cnc.Connection:
pktConn = &internet.FakePacketConn{Conn: c}
udpAddr = &net.UDPAddr{IP: c.RemoteAddr().(*net.TCPAddr).IP, Port: c.RemoteAddr().(*net.TCPAddr).Port}
default:
panic(reflect.TypeOf(c))
}
newConn, err := streamSettings.UdpmaskManager.WrapPacketConnClient(pktConn)
if err != nil {
pktConn.Close()
return nil, errors.New("mask err").Base(err)
}
pktConn = newConn
conn = &internet.PacketConnWrapper{
PacketConn: pktConn,
Dest: udpAddr,
}
}
return conn, nil
}))
}
+14 -5
View File
@@ -58,15 +58,24 @@ func ListenUDP(ctx context.Context, address net.Address, port net.Port, streamSe
}
var err error
if streamSettings.FinalMask != nil {
hub.conn, err = streamSettings.FinalMask.ListenPacket(ctx, &net.UDPAddr{IP: address.IP(), Port: int(port)})
} else {
hub.conn, err = internet.ListenSystemPacket(ctx, &net.UDPAddr{IP: address.IP(), Port: int(port)}, streamSettings.SocketSettings)
}
hub.conn, err = internet.ListenSystemPacket(ctx, &net.UDPAddr{
IP: address.IP(),
Port: int(port),
}, sockopt)
if err != nil {
return nil, err
}
raw := hub.conn
if streamSettings.UdpmaskManager != nil {
hub.conn, err = streamSettings.UdpmaskManager.WrapPacketConnServer(raw)
if err != nil {
raw.Close()
return nil, errors.New("mask err").Base(err)
}
}
errors.LogInfo(ctx, "listening UDP on ", address, ":", port)
hub.udpConn, _ = hub.conn.(*net.UDPConn)
hub.cache = make(chan *udp.Packet, hub.capacity)

Some files were not shown because too many files have changed in this diff Show More