mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-09-16 06:20:28 +00:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
212d1b108b | ||
|
|
2101c1a99f | ||
|
|
e1fe8be7a5 | ||
|
|
bbc8103a7d |
@@ -1 +0,0 @@
|
||||
powershell.exe -ExecutionPolicy Bypass -File ".\xray_no_window.ps1"
|
||||
@@ -1 +0,0 @@
|
||||
Start-Process -FilePath ".\xray.exe" -ArgumentList "-config .\config.json" -WindowStyle Hidden
|
||||
@@ -1 +0,0 @@
|
||||
CreateObject("Wscript.Shell").Run "xray.exe -config config.json",0
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
echo "LATEST=$LATEST" >>${GITHUB_ENV}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v4
|
||||
|
||||
@@ -83,7 +83,7 @@ jobs:
|
||||
CGO_ENABLED: 0
|
||||
steps:
|
||||
- name: Checkout codebase
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Show workflow information
|
||||
run: |
|
||||
@@ -134,17 +134,15 @@ jobs:
|
||||
run: |
|
||||
mv -f resources/geo* build_assets/
|
||||
if [[ ${GOOS} == 'windows' ]]; then
|
||||
cp .github/build/windows/* build_assets/
|
||||
fi
|
||||
if [[ ${GOOS} == 'windows' ]]; then
|
||||
echo 'Adding Wintun into packages'
|
||||
echo 'CreateObject("Wscript.Shell").Run "xray.exe -config config.json",0' > build_assets/xray_no_window.vbs
|
||||
echo 'Start-Process -FilePath ".\xray.exe" -ArgumentList "-config .\config.json" -WindowStyle Hidden' > build_assets/xray_no_window.ps1
|
||||
if [[ ${GOARCH} == 'amd64' ]]; then
|
||||
mv resources/wintun/bin/amd64/wintun.dll build_assets/
|
||||
fi
|
||||
if [[ ${GOARCH} == '386' ]]; then
|
||||
mv resources/wintun/bin/x86/wintun.dll build_assets/
|
||||
fi
|
||||
mv resources/wintun/LICENSE.txt build_assets/LICENSE-Wintun
|
||||
mv resources/wintun/LICENSE.txt build_assets/LICENSE-wintun.txt
|
||||
fi
|
||||
|
||||
- name: Copy README.md & LICENSE
|
||||
|
||||
@@ -170,7 +170,7 @@ jobs:
|
||||
CGO_ENABLED: 0
|
||||
steps:
|
||||
- name: Checkout codebase
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up NDK
|
||||
if: matrix.goos == 'android'
|
||||
@@ -241,10 +241,8 @@ jobs:
|
||||
run: |
|
||||
mv -f resources/geo* build_assets/
|
||||
if [[ ${GOOS} == 'windows' ]]; then
|
||||
cp .github/build/windows/* build_assets/
|
||||
fi
|
||||
if [[ ${GOOS} == 'windows' ]]; then
|
||||
echo 'Adding Wintun into packages'
|
||||
echo 'CreateObject("Wscript.Shell").Run "xray.exe -config config.json",0' > build_assets/xray_no_window.vbs
|
||||
echo 'Start-Process -FilePath ".\xray.exe" -ArgumentList "-config .\config.json" -WindowStyle Hidden' > build_assets/xray_no_window.ps1
|
||||
if [[ ${GOARCH} == 'amd64' ]]; then
|
||||
mv resources/wintun/bin/amd64/wintun.dll build_assets/
|
||||
fi
|
||||
@@ -254,7 +252,7 @@ jobs:
|
||||
if [[ ${GOARCH} == 'arm64' ]]; then
|
||||
mv resources/wintun/bin/arm64/wintun.dll build_assets/
|
||||
fi
|
||||
mv resources/wintun/LICENSE.txt build_assets/LICENSE-Wintun
|
||||
mv resources/wintun/LICENSE.txt build_assets/LICENSE-wintun.txt
|
||||
fi
|
||||
|
||||
- name: Copy README.md & LICENSE
|
||||
|
||||
@@ -68,9 +68,6 @@ jobs:
|
||||
wintun:
|
||||
if: github.event.schedule == '30 22 * * *' || github.event_name == 'push' || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch'
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
ASSETVER: 0.14.1
|
||||
ASSETHASH: 07c256185d6ee3652e09fa55c0b673e2624b565e02c4b9091c79ca7d2f24ef51
|
||||
steps:
|
||||
- name: Restore Wintun Cache
|
||||
uses: actions/cache/restore@v5
|
||||
@@ -99,6 +96,7 @@ jobs:
|
||||
echo -e "Checking if wintun.dll for ${ARCHITECTURE} exists..."
|
||||
if [ -s "./resources/wintun/bin/${ARCHITECTURE}/wintun.dll" ]; then
|
||||
echo -e "wintun.dll for ${ARCHITECTURE} exists"
|
||||
continue
|
||||
else
|
||||
echo -e "wintun.dll for ${ARCHITECTURE} is missing"
|
||||
missing=true
|
||||
@@ -115,17 +113,12 @@ jobs:
|
||||
fi
|
||||
if [[ "$missing" == true ]]; then
|
||||
FILENAME=wintun.zip
|
||||
DOWNLOAD_FILE=wintun-${ASSETVER}.zip
|
||||
DOWNLOAD_FILE=wintun-0.14.1.zip
|
||||
echo -e "Downloading https://www.wintun.net/builds/${DOWNLOAD_FILE}..."
|
||||
curl -L "https://www.wintun.net/builds/${DOWNLOAD_FILE}" -o "${FILENAME}"
|
||||
if [[ "$(sha256sum "./${FILENAME}" | awk -F ' ' '{print $1}')" == "${ASSETHASH}" ]]; then
|
||||
echo -e "Unpacking wintun..."
|
||||
unzip -u ${FILENAME} -d resources/
|
||||
echo "unhit=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo -e "Digest of ${FILENAME} mismatch."
|
||||
exit 1
|
||||
fi
|
||||
echo -e "Unpacking wintun..."
|
||||
unzip -u ${FILENAME} -d resources/
|
||||
echo "unhit=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Save Wintun Cache
|
||||
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||
steps:
|
||||
- name: Checkout codebase
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v6
|
||||
- name: Check Proto Version Header
|
||||
run: |
|
||||
head -n 4 core/config.pb.go > ref.txt
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout codebase
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v6
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
@@ -83,7 +83,7 @@ jobs:
|
||||
os: [windows-latest, ubuntu-latest, macos-latest]
|
||||
steps:
|
||||
- name: Checkout codebase
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v6
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
|
||||
@@ -186,27 +186,6 @@
|
||||
- [Xray-core v1.0.0](https://github.com/XTLS/Xray-core/releases/tag/v1.0.0) was forked from [v2fly-core 9a03cc5](https://github.com/v2fly/v2ray-core/commit/9a03cc5c98d04cc28320fcee26dbc236b3291256), and we have made & accumulated a huge number of enhancements over time, check [the release notes for each version](https://github.com/XTLS/Xray-core/releases).
|
||||
- For third-party projects used in [Xray-core](https://github.com/XTLS/Xray-core), check your local or [the latest go.mod](https://github.com/XTLS/Xray-core/blob/main/go.mod).
|
||||
|
||||
### Bundled Third-Party Components Redistribution
|
||||
|
||||
**Certain optional features dynamically load third-party components. These optional components are separate works distributed under their own licenses, and are bundled into the ZIP package for ease of use. Users may replace these components under the licenses from these components.**
|
||||
|
||||
These components include:
|
||||
|
||||
#### Wintun
|
||||
|
||||
This distribution contains unmodified official precompiled and pre-signed Wintun binaries.
|
||||
|
||||
- Project: Wintun
|
||||
- Copyright: Copyright (C) 2018-2021 WireGuard LLC. All Rights Reserved.
|
||||
- Redistribution License: Prebuilt Binaries License (PBL) bundled with official precompiled and pre-signed binaries from wintun.net
|
||||
- Component(s): wintun.dll
|
||||
- Source: https://www.wintun.net/
|
||||
- Included in:
|
||||
- Windows x86 (windows-32, win7-32)
|
||||
- Windows x86-64 (windows-64, win7-64)
|
||||
- Windows AArch64 (windows-arm64)
|
||||
- Notes: Wintun is an optional runtime-loaded component only used for TUN inbound functionality on supported Windows platforms.
|
||||
|
||||
## One-line Compilation
|
||||
|
||||
### Windows (PowerShell)
|
||||
|
||||
@@ -57,23 +57,16 @@ func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
src := net.TCPDestination(net.AnyIP, 0)
|
||||
if receiverConfig.Listen != nil {
|
||||
src.Address = receiverConfig.Listen.AsAddress()
|
||||
}
|
||||
if receiverConfig.PortList != nil && len(receiverConfig.PortList.Range) > 0 {
|
||||
src.Port = net.Port(receiverConfig.PortList.Range[0].From)
|
||||
}
|
||||
mss, err := internet.ToMemoryStreamConfig(receiverConfig.StreamSettings)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to parse stream config").Base(err).AtWarning()
|
||||
}
|
||||
|
||||
newCtx := session.ContextWithInbound(ctx, &session.Inbound{Tag: tag, Source: src})
|
||||
newCtx = session.ContextWithContent(newCtx, &session.Content{SniffingRequest: sniffingRequest})
|
||||
newCtx = session.ContextWithStreamSettings(newCtx, mss)
|
||||
|
||||
rawProxy, err := common.CreateObject(newCtx, proxyConfig)
|
||||
// Set tag and sniffing config in context before creating proxy
|
||||
// This allows proxies like TUN to access these settings
|
||||
ctx = session.ContextWithInbound(ctx, &session.Inbound{Tag: tag})
|
||||
if receiverConfig.SniffingSettings != nil {
|
||||
ctx = session.ContextWithContent(ctx, &session.Content{
|
||||
SniffingRequest: sniffingRequest,
|
||||
})
|
||||
}
|
||||
rawProxy, err := common.CreateObject(ctx, proxyConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -99,6 +92,11 @@ func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *
|
||||
address = net.AnyIP
|
||||
}
|
||||
|
||||
mss, err := internet.ToMemoryStreamConfig(receiverConfig.StreamSettings)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to parse stream config").Base(err).AtWarning()
|
||||
}
|
||||
|
||||
if receiverConfig.ReceiveOriginalDestination {
|
||||
if mss.SocketSettings == nil {
|
||||
mss.SocketSettings = &internet.SocketConfig{}
|
||||
@@ -172,12 +170,6 @@ func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *
|
||||
|
||||
// Start implements common.Runnable.
|
||||
func (h *AlwaysOnInboundHandler) Start() error {
|
||||
// for inbound without worker (TUN)
|
||||
if run, ok := h.proxy.(common.Runnable); ok {
|
||||
if err := run.Start(); err != nil {
|
||||
return errors.New("failed to start proxy").Base(err)
|
||||
}
|
||||
}
|
||||
for _, worker := range h.workers {
|
||||
if err := worker.Start(); err != nil {
|
||||
return err
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
goerrors "errors"
|
||||
"io"
|
||||
"math/big"
|
||||
"os"
|
||||
|
||||
"github.com/xtls/xray-core/common/dice"
|
||||
|
||||
@@ -108,9 +109,7 @@ func NewHandler(ctx context.Context, config *core.OutboundHandlerConfig) (outbou
|
||||
|
||||
ctx = session.ContextWithFullHandler(ctx, h)
|
||||
|
||||
newCtx := session.ContextWithStreamSettings(ctx, h.streamSettings)
|
||||
|
||||
rawProxyHandler, err := common.CreateObject(newCtx, proxyConfig)
|
||||
rawProxyHandler, err := common.CreateObject(ctx, proxyConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -307,6 +306,11 @@ func (h *Handler) Dial(ctx context.Context, dest net.Destination) (stat.Connecti
|
||||
ob := outbounds[len(outbounds)-1]
|
||||
h.SetOutboundGateway(ctx, ob)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if conn, err := h.getUoTConnection(ctx, dest); err != os.ErrInvalid {
|
||||
return conn, err
|
||||
}
|
||||
|
||||
conn, err := internet.Dial(ctx, dest, h.streamSettings)
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"github.com/sagernet/sing/common/uot"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/transport/internet"
|
||||
"github.com/xtls/xray-core/transport/internet/stat"
|
||||
)
|
||||
|
||||
func (h *Handler) getUoTConnection(ctx context.Context, dest net.Destination) (stat.Connection, error) {
|
||||
if dest.Address == nil {
|
||||
return nil, errors.New("nil destination address")
|
||||
}
|
||||
if !dest.Address.Family().IsDomain() {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
var uotVersion int
|
||||
if dest.Address.Domain() == uot.MagicAddress {
|
||||
uotVersion = uot.Version
|
||||
} else if dest.Address.Domain() == uot.LegacyMagicAddress {
|
||||
uotVersion = uot.LegacyVersion
|
||||
} else {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
packetConn, err := internet.ListenSystemPacket(ctx, &net.UDPAddr{IP: net.AnyIP.IP(), Port: 0}, h.streamSettings.SocketSettings)
|
||||
if err != nil {
|
||||
return nil, errors.New("unable to listen socket").Base(err)
|
||||
}
|
||||
conn := uot.NewServerConn(packetConn, uotVersion)
|
||||
return h.getStatCouterConnection(conn), nil
|
||||
}
|
||||
@@ -220,7 +220,7 @@ func parseDomain(d *Domain) (strmatcher.Matcher, error) {
|
||||
case Domain_Regex:
|
||||
return strmatcher.Regex.New(d.Value)
|
||||
case Domain_Domain:
|
||||
return strmatcher.Domain.New(strings.ToLower(d.Value))
|
||||
return strmatcher.Domain.New(d.Value)
|
||||
case Domain_Full:
|
||||
return strmatcher.Full.New(strings.ToLower(d.Value))
|
||||
default:
|
||||
|
||||
@@ -6,14 +6,12 @@ import (
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/utils"
|
||||
"github.com/xtls/xray-core/common/uuid"
|
||||
)
|
||||
|
||||
type DomainRegistry struct {
|
||||
mu sync.Mutex
|
||||
factory DomainMatcherFactory
|
||||
matchers *utils.WeakCacheMap[uuid.UUID, DynamicDomainMatcher]
|
||||
matchers []*DynamicDomainMatcher
|
||||
}
|
||||
|
||||
func (r *DomainRegistry) BuildDomainMatcher(rules []*DomainRule) (DomainMatcher, error) {
|
||||
@@ -26,7 +24,7 @@ func (r *DomainRegistry) BuildDomainMatcher(rules []*DomainRule) (DomainMatcher,
|
||||
}
|
||||
|
||||
d := NewDynamicDomainMatcher(rules, m)
|
||||
r.matchers.Store(uuid.New(), d)
|
||||
r.matchers = append(r.matchers, d)
|
||||
return d, nil
|
||||
}
|
||||
|
||||
@@ -34,20 +32,15 @@ func (r *DomainRegistry) Reload() error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
var matchers []*DynamicDomainMatcher
|
||||
r.matchers.Range(func(_ uuid.UUID, matcher *DynamicDomainMatcher) bool {
|
||||
matchers = append(matchers, matcher)
|
||||
return true
|
||||
})
|
||||
errors.LogInfo(context.Background(), "reloading GeoSite data for ", len(matchers), " domain matcher(s)")
|
||||
errors.LogInfo(context.Background(), "reloading GeoSite data for ", len(r.matchers), " domain matcher(s)")
|
||||
|
||||
factory := newDomainMatcherFactory()
|
||||
type reloadEntry struct {
|
||||
dynamic *DynamicDomainMatcher
|
||||
matcher DomainMatcher
|
||||
}
|
||||
reloaded := make([]reloadEntry, len(matchers))
|
||||
for i, d := range matchers {
|
||||
reloaded := make([]reloadEntry, len(r.matchers))
|
||||
for i, d := range r.matchers {
|
||||
m, err := factory.BuildMatcher(d.rules)
|
||||
if err != nil {
|
||||
errors.LogErrorInner(context.Background(), err, "failed to reload GeoSite data for domain matcher ", i)
|
||||
@@ -59,14 +52,13 @@ func (r *DomainRegistry) Reload() error {
|
||||
entry.dynamic.Reload(entry.matcher)
|
||||
}
|
||||
r.factory = factory
|
||||
errors.LogInfo(context.Background(), "reloaded GeoSite data for ", len(matchers), " domain matcher(s)")
|
||||
errors.LogInfo(context.Background(), "reloaded GeoSite data for ", len(r.matchers), " domain matcher(s)")
|
||||
return nil
|
||||
}
|
||||
|
||||
func newDomainRegistry() *DomainRegistry {
|
||||
return &DomainRegistry{
|
||||
factory: newDomainMatcherFactory(),
|
||||
matchers: utils.NewWeakCacheMap[uuid.UUID, DynamicDomainMatcher](),
|
||||
factory: newDomainMatcherFactory(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,27 +7,25 @@ import (
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/utils"
|
||||
"github.com/xtls/xray-core/common/uuid"
|
||||
)
|
||||
|
||||
type IPRegistry struct {
|
||||
mu sync.Mutex
|
||||
factory *IPSetFactory
|
||||
matchers *utils.WeakCacheMap[uuid.UUID, DynamicIPMatcher]
|
||||
mu sync.Mutex
|
||||
ipsetFactory *IPSetFactory
|
||||
matchers []*DynamicIPMatcher
|
||||
}
|
||||
|
||||
func (r *IPRegistry) BuildIPMatcher(rules []*IPRule) (IPMatcher, error) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
m, err := buildOptimizedIPMatcher(r.factory, rules)
|
||||
m, err := buildOptimizedIPMatcher(r.ipsetFactory, rules)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d := NewDynamicIPMatcher(rules, m)
|
||||
r.matchers.Store(uuid.New(), d)
|
||||
r.matchers = append(r.matchers, d)
|
||||
return d, nil
|
||||
}
|
||||
|
||||
@@ -35,20 +33,15 @@ func (r *IPRegistry) Reload() error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
var matchers []*DynamicIPMatcher
|
||||
r.matchers.Range(func(_ uuid.UUID, matcher *DynamicIPMatcher) bool {
|
||||
matchers = append(matchers, matcher)
|
||||
return true
|
||||
})
|
||||
errors.LogInfo(context.Background(), "reloading GeoIP data for ", len(matchers), " IP matcher(s)")
|
||||
errors.LogInfo(context.Background(), "reloading GeoIP data for ", len(r.matchers), " IP matcher(s)")
|
||||
|
||||
factory := newIPSetFactory()
|
||||
type reloadEntry struct {
|
||||
dynamic *DynamicIPMatcher
|
||||
matcher IPMatcher
|
||||
}
|
||||
reloaded := make([]reloadEntry, len(matchers))
|
||||
for i, d := range matchers {
|
||||
reloaded := make([]reloadEntry, len(r.matchers))
|
||||
for i, d := range r.matchers {
|
||||
m, err := buildOptimizedIPMatcher(factory, d.rules)
|
||||
if err != nil {
|
||||
errors.LogErrorInner(context.Background(), err, "failed to reload GeoIP data for IP matcher ", i)
|
||||
@@ -59,15 +52,14 @@ func (r *IPRegistry) Reload() error {
|
||||
for _, entry := range reloaded {
|
||||
entry.dynamic.Reload(entry.matcher)
|
||||
}
|
||||
r.factory = factory
|
||||
errors.LogInfo(context.Background(), "reloaded GeoIP data for ", len(matchers), " IP matcher(s)")
|
||||
r.ipsetFactory = factory
|
||||
errors.LogInfo(context.Background(), "reloaded GeoIP data for ", len(r.matchers), " IP matcher(s)")
|
||||
return nil
|
||||
}
|
||||
|
||||
func newIPRegistry() *IPRegistry {
|
||||
return &IPRegistry{
|
||||
factory: newIPSetFactory(),
|
||||
matchers: utils.NewWeakCacheMap[uuid.UUID, DynamicIPMatcher](),
|
||||
ipsetFactory: newIPSetFactory(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -138,7 +138,7 @@ func ParseDomainRule(r string, defaultType Domain_Type) (*DomainRule, error) {
|
||||
}
|
||||
|
||||
prefix := 0
|
||||
for _, ext := range [...]string{"ext:", "ext-domain:", "ext-site:"} {
|
||||
for _, ext := range [...]string{"ext:", "ext-domain:"} {
|
||||
if strings.HasPrefix(r, ext) {
|
||||
prefix = len(ext)
|
||||
break
|
||||
@@ -167,7 +167,7 @@ func ParseDomainRules(rules []string, defaultType Domain_Type) ([]*DomainRule, e
|
||||
}
|
||||
|
||||
prefix := 0
|
||||
for _, ext := range [...]string{"ext:", "ext-domain:", "ext-site:"} {
|
||||
for _, ext := range [...]string{"ext:", "ext-domain:"} {
|
||||
if strings.HasPrefix(r, ext) {
|
||||
prefix = len(ext)
|
||||
break
|
||||
|
||||
@@ -1,41 +1,25 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
)
|
||||
|
||||
// ApplyTrustedXForwardedFor returns remoteAddr overridden by X-Forwarded-For only when a configured trusted header is present.
|
||||
func ApplyTrustedXForwardedFor(header http.Header, trusted []string, remoteAddr net.Addr) net.Addr {
|
||||
value := header.Get("X-Forwarded-For")
|
||||
if value == "" {
|
||||
return remoteAddr
|
||||
// ParseXForwardedFor parses X-Forwarded-For header in http headers, and return the IP list in it.
|
||||
func ParseXForwardedFor(header http.Header) []net.Address {
|
||||
xff := header.Get("X-Forwarded-For")
|
||||
if xff == "" {
|
||||
return nil
|
||||
}
|
||||
for _, t := range trusted {
|
||||
if len(header.Values(t)) > 0 {
|
||||
if idx := strings.IndexByte(value, ','); idx >= 0 {
|
||||
value = value[:idx]
|
||||
}
|
||||
if addr := net.ParseAddress(value); addr.Family().IsIP() {
|
||||
return &net.TCPAddr{
|
||||
IP: addr.IP(),
|
||||
Port: 0,
|
||||
}
|
||||
}
|
||||
return remoteAddr
|
||||
}
|
||||
list := strings.Split(xff, ",")
|
||||
addrs := make([]net.Address, 0, len(list))
|
||||
for _, proxy := range list {
|
||||
addrs = append(addrs, net.ParseAddress(proxy))
|
||||
}
|
||||
if len(trusted) == 0 {
|
||||
errors.LogWarning(context.Background(), `received "X-Forwarded-For" from `, remoteAddr, ` but "sockopt.trustedXForwardedFor" is not configured; ignoring it and using the real remote address`)
|
||||
} else {
|
||||
errors.LogError(context.Background(), `ignored potentially forged "X-Forwarded-For" from `, remoteAddr, `: `, value)
|
||||
}
|
||||
return remoteAddr
|
||||
return addrs
|
||||
}
|
||||
|
||||
// RemoveHopByHopHeaders removes hop by hop headers in http header list.
|
||||
|
||||
@@ -2,48 +2,23 @@ package http_test
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
gonet "net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
. "github.com/xtls/xray-core/common/protocol/http"
|
||||
)
|
||||
|
||||
func TestApplyTrustedXForwardedFor(t *testing.T) {
|
||||
remoteAddr := &gonet.TCPAddr{IP: gonet.ParseIP("127.0.0.1"), Port: 12345}
|
||||
|
||||
t.Run("ignore X-Forwarded-For without trusted header", func(t *testing.T) {
|
||||
header := http.Header{}
|
||||
header.Add("X-Forwarded-For", "129.78.138.66, 129.78.64.103")
|
||||
|
||||
if addr := ApplyTrustedXForwardedFor(header, nil, remoteAddr); addr != remoteAddr {
|
||||
t.Fatalf("unexpected remote address: %v", addr)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("trust X-Forwarded-For", func(t *testing.T) {
|
||||
header := http.Header{}
|
||||
header.Add("X-Forwarded-For", "129.78.138.66, 129.78.64.103")
|
||||
header.Add("X-Trusted-CDN", "")
|
||||
|
||||
addr := ApplyTrustedXForwardedFor(header, []string{"X-Trusted-CDN"}, remoteAddr)
|
||||
if addr.String() != "129.78.138.66:0" {
|
||||
t.Fatalf("unexpected remote address: %v", addr)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ignore non-IP X-Forwarded-For", func(t *testing.T) {
|
||||
header := http.Header{}
|
||||
header.Add("X-Forwarded-For", "example.com")
|
||||
header.Add("X-Trusted-CDN", "")
|
||||
|
||||
if addr := ApplyTrustedXForwardedFor(header, []string{"X-Trusted-CDN"}, remoteAddr); addr != remoteAddr {
|
||||
t.Fatalf("unexpected remote address: %v", addr)
|
||||
}
|
||||
})
|
||||
func TestParseXForwardedFor(t *testing.T) {
|
||||
header := http.Header{}
|
||||
header.Add("X-Forwarded-For", "129.78.138.66, 129.78.64.103")
|
||||
addrs := ParseXForwardedFor(header)
|
||||
if r := cmp.Diff(addrs, []net.Address{net.ParseAddress("129.78.138.66"), net.ParseAddress("129.78.64.103")}); r != "" {
|
||||
t.Error(r)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHopByHopHeadersRemoving(t *testing.T) {
|
||||
|
||||
@@ -26,8 +26,6 @@ const (
|
||||
fullHandlerKey ctx.SessionKey = 10 // outbound gets full handler
|
||||
mitmAlpn11Key ctx.SessionKey = 11 // used by TLS dialer
|
||||
mitmServerNameKey ctx.SessionKey = 12 // used by TLS dialer
|
||||
|
||||
streamSettingsKey ctx.SessionKey = 13
|
||||
)
|
||||
|
||||
func ContextWithInbound(ctx context.Context, inbound *Inbound) context.Context {
|
||||
@@ -194,11 +192,3 @@ func MitmServerNameFromContext(ctx context.Context) string {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func ContextWithStreamSettings(ctx context.Context, streamSettings any) context.Context {
|
||||
return context.WithValue(ctx, streamSettingsKey, streamSettings)
|
||||
}
|
||||
|
||||
func StreamSettingsFromContext(ctx context.Context) any {
|
||||
return ctx.Value(streamSettingsKey)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"maps"
|
||||
"runtime"
|
||||
"sync"
|
||||
"weak"
|
||||
@@ -44,16 +43,3 @@ func (c *WeakCacheMap[K, V]) Store(key K, value *V) {
|
||||
}
|
||||
}, struct{}{})
|
||||
}
|
||||
|
||||
func (c *WeakCacheMap[K, V]) Range(f func(K, *V) bool) {
|
||||
c.mu.Lock()
|
||||
snapshot := maps.Clone(c.m)
|
||||
c.mu.Unlock()
|
||||
for k, v := range snapshot {
|
||||
if value := v.Value(); value != nil {
|
||||
if !f(k, value) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ import (
|
||||
var (
|
||||
Version_x byte = 26
|
||||
Version_y byte = 6
|
||||
Version_z byte = 22
|
||||
Version_z byte = 1
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -12,7 +12,7 @@ require (
|
||||
github.com/klauspost/cpuid/v2 v2.3.0
|
||||
github.com/miekg/dns v1.1.72
|
||||
github.com/pelletier/go-toml v1.9.5
|
||||
github.com/pion/stun/v3 v3.1.6
|
||||
github.com/pion/stun/v3 v3.1.2
|
||||
github.com/pires/go-proxyproto v0.12.0
|
||||
github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
@@ -22,11 +22,11 @@ require (
|
||||
github.com/vishvananda/netlink v1.3.1
|
||||
github.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||
golang.org/x/crypto v0.53.0
|
||||
golang.org/x/crypto v0.51.0
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
|
||||
golang.org/x/net v0.56.0
|
||||
golang.org/x/sync v0.21.0
|
||||
golang.org/x/sys v0.46.0
|
||||
golang.org/x/net v0.55.0
|
||||
golang.org/x/sync v0.20.0
|
||||
golang.org/x/sys v0.45.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.0.1
|
||||
@@ -44,17 +44,17 @@ require (
|
||||
github.com/juju/ratelimit v1.0.2 // indirect
|
||||
github.com/klauspost/compress v1.17.4 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/pion/dtls/v3 v3.1.4 // indirect
|
||||
github.com/pion/dtls/v3 v3.1.2 // indirect
|
||||
github.com/pion/logging v0.2.4 // indirect
|
||||
github.com/pion/transport/v4 v4.0.2 // indirect
|
||||
github.com/pion/transport/v4 v4.0.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/quic-go/qpack v0.6.0 // indirect
|
||||
github.com/vishvananda/netns v0.0.5 // indirect
|
||||
github.com/wlynxg/anet v0.0.5 // indirect
|
||||
golang.org/x/mod v0.36.0 // indirect
|
||||
golang.org/x/text v0.38.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
golang.org/x/tools v0.45.0 // indirect
|
||||
golang.org/x/mod v0.35.0 // indirect
|
||||
golang.org/x/text v0.37.0 // indirect
|
||||
golang.org/x/time v0.12.0 // indirect
|
||||
golang.org/x/tools v0.44.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
||||
@@ -45,14 +45,14 @@ github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3v
|
||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc=
|
||||
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
|
||||
github.com/pion/dtls/v3 v3.1.4 h1:QhvtMflMfu9Kf0RcDC5BJBle4caPskByrKQR6uuYqpY=
|
||||
github.com/pion/dtls/v3 v3.1.4/go.mod h1:cr/qotLISUw/9C1m83ZPNZtj9WnXkYLpfCptPqbkInc=
|
||||
github.com/pion/dtls/v3 v3.1.2 h1:gqEdOUXLtCGW+afsBLO0LtDD8GnuBBjEy6HRtyofZTc=
|
||||
github.com/pion/dtls/v3 v3.1.2/go.mod h1:Hw/igcX4pdY69z1Hgv5x7wJFrUkdgHwAn/Q/uo7YHRo=
|
||||
github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8=
|
||||
github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so=
|
||||
github.com/pion/stun/v3 v3.1.6 h1:WnhsD0eHCiwCfKNkVx0VJJwr2Y3eV4Ueih3KJ+dfZy8=
|
||||
github.com/pion/stun/v3 v3.1.6/go.mod h1:zRUghXSQU32Lx5orJsz3uYMkIihweXb3mu5gIns02fs=
|
||||
github.com/pion/transport/v4 v4.0.2 h1:ifYlPqNwsy6aKQ9y8yzxXlHae5431ZrH2avkD/Rn6Tk=
|
||||
github.com/pion/transport/v4 v4.0.2/go.mod h1:06hFI+jCFcok2X2MekVufNZ/uzNZXivGBPfviSVcjgM=
|
||||
github.com/pion/stun/v3 v3.1.2 h1:86IhD8wFn6IDW4b1/0QzoQS+f5PeA8OHHRn8UZW5ErY=
|
||||
github.com/pion/stun/v3 v3.1.2/go.mod h1:H7gDic7nNwlUL05pbs6T1dtaBehh/KjupxfWw3ZI7cA=
|
||||
github.com/pion/transport/v4 v4.0.1 h1:sdROELU6BZ63Ab7FrOLn13M6YdJLY20wldXW2Cu2k8o=
|
||||
github.com/pion/transport/v4 v4.0.1/go.mod h1:nEuEA4AD5lPdcIegQDpVLgNoDGreqM/YqmEx3ovP4jM=
|
||||
github.com/pires/go-proxyproto v0.12.0 h1:TTCxD66dU898tahivkqc3hoceZp7P44FnorWyo9d5vM=
|
||||
github.com/pires/go-proxyproto v0.12.0/go.mod h1:qUvfqUMEoX7T8g0q7TQLDnhMjdTrxnG0hvpMn+7ePNI=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
@@ -98,22 +98,22 @@ 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.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
|
||||
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
|
||||
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
|
||||
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
|
||||
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/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
|
||||
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
|
||||
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
|
||||
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
|
||||
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.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
|
||||
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
|
||||
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
|
||||
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
|
||||
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.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
|
||||
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.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=
|
||||
@@ -121,21 +121,21 @@ 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.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
|
||||
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
||||
golang.org/x/sys v0.45.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.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
|
||||
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
|
||||
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/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
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.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
||||
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
|
||||
golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
|
||||
golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
|
||||
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
|
||||
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=
|
||||
|
||||
+2
-12
@@ -1,24 +1,14 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/proxy/loopback"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
type LoopbackConfig struct {
|
||||
InboundTag string `json:"inboundTag"`
|
||||
Sniffing *SniffingConfig `json:"sniffing"`
|
||||
InboundTag string `json:"inboundTag"`
|
||||
}
|
||||
|
||||
func (l LoopbackConfig) Build() (proto.Message, error) {
|
||||
c := &loopback.Config{InboundTag: l.InboundTag}
|
||||
if l.Sniffing != nil {
|
||||
sc, err := l.Sniffing.Build()
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to build sniffing config").Base(err)
|
||||
}
|
||||
c.Sniffing = sc
|
||||
}
|
||||
return c, nil
|
||||
return &loopback.Config{InboundTag: l.InboundTag}, nil
|
||||
}
|
||||
|
||||
+27
-19
@@ -179,22 +179,26 @@ func buildShadowsocks2022(v *ShadowsocksServerConfig) (proto.Message, error) {
|
||||
}
|
||||
|
||||
type ShadowsocksServerTarget struct {
|
||||
Address *Address `json:"address"`
|
||||
Port uint16 `json:"port"`
|
||||
Level byte `json:"level"`
|
||||
Email string `json:"email"`
|
||||
Cipher string `json:"method"`
|
||||
Password string `json:"password"`
|
||||
Address *Address `json:"address"`
|
||||
Port uint16 `json:"port"`
|
||||
Level byte `json:"level"`
|
||||
Email string `json:"email"`
|
||||
Cipher string `json:"method"`
|
||||
Password string `json:"password"`
|
||||
UoT bool `json:"uot"`
|
||||
UoTVersion int `json:"uotVersion"`
|
||||
}
|
||||
|
||||
type ShadowsocksClientConfig struct {
|
||||
Address *Address `json:"address"`
|
||||
Port uint16 `json:"port"`
|
||||
Level byte `json:"level"`
|
||||
Email string `json:"email"`
|
||||
Cipher string `json:"method"`
|
||||
Password string `json:"password"`
|
||||
Servers []*ShadowsocksServerTarget `json:"servers"`
|
||||
Address *Address `json:"address"`
|
||||
Port uint16 `json:"port"`
|
||||
Level byte `json:"level"`
|
||||
Email string `json:"email"`
|
||||
Cipher string `json:"method"`
|
||||
Password string `json:"password"`
|
||||
UoT bool `json:"uot"`
|
||||
UoTVersion int `json:"uotVersion"`
|
||||
Servers []*ShadowsocksServerTarget `json:"servers"`
|
||||
}
|
||||
|
||||
func (v *ShadowsocksClientConfig) Build() (proto.Message, error) {
|
||||
@@ -203,12 +207,14 @@ func (v *ShadowsocksClientConfig) Build() (proto.Message, error) {
|
||||
if v.Address != nil {
|
||||
v.Servers = []*ShadowsocksServerTarget{
|
||||
{
|
||||
Address: v.Address,
|
||||
Port: v.Port,
|
||||
Level: v.Level,
|
||||
Email: v.Email,
|
||||
Cipher: v.Cipher,
|
||||
Password: v.Password,
|
||||
Address: v.Address,
|
||||
Port: v.Port,
|
||||
Level: v.Level,
|
||||
Email: v.Email,
|
||||
Cipher: v.Cipher,
|
||||
Password: v.Password,
|
||||
UoT: v.UoT,
|
||||
UoTVersion: v.UoTVersion,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -234,6 +240,8 @@ func (v *ShadowsocksClientConfig) Build() (proto.Message, error) {
|
||||
config.Port = uint32(server.Port)
|
||||
config.Method = server.Cipher
|
||||
config.Key = server.Password
|
||||
config.UdpOverTcp = server.UoT
|
||||
config.UdpOverTcpVersion = uint32(server.UoTVersion)
|
||||
return config, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"math"
|
||||
"math/big"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"os"
|
||||
@@ -220,10 +219,8 @@ type SplitHTTPConfig struct {
|
||||
XPaddingPlacement string `json:"xPaddingPlacement"`
|
||||
XPaddingMethod string `json:"xPaddingMethod"`
|
||||
UplinkHTTPMethod string `json:"uplinkHTTPMethod"`
|
||||
SessionIDPlacement string `json:"sessionIDPlacement"`
|
||||
SessionIDKey string `json:"sessionIDKey"`
|
||||
SessionIDTable string `json:"sessionIDTable"`
|
||||
SessionIDLength Int32Range `json:"sessionIDLength"`
|
||||
SessionPlacement string `json:"sessionPlacement"`
|
||||
SessionKey string `json:"sessionKey"`
|
||||
SeqPlacement string `json:"seqPlacement"`
|
||||
SeqKey string `json:"seqKey"`
|
||||
UplinkDataPlacement string `json:"uplinkDataPlacement"`
|
||||
@@ -334,12 +331,12 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
|
||||
return nil, errors.New("uplinkHTTPMethod can be GET only in packet-up mode")
|
||||
}
|
||||
|
||||
switch c.SessionIDPlacement {
|
||||
switch c.SessionPlacement {
|
||||
case "":
|
||||
c.SessionIDPlacement = "path"
|
||||
c.SessionPlacement = "path"
|
||||
case "path", "cookie", "header", "query":
|
||||
default:
|
||||
return nil, errors.New("unsupported session placement: " + c.SessionIDPlacement)
|
||||
return nil, errors.New("unsupported session placement: " + c.SessionPlacement)
|
||||
}
|
||||
|
||||
switch c.SeqPlacement {
|
||||
@@ -350,31 +347,12 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
|
||||
return nil, errors.New("unsupported seq placement: " + c.SeqPlacement)
|
||||
}
|
||||
|
||||
if c.SessionIDPlacement != "path" && c.SessionIDKey == "" {
|
||||
switch c.SessionIDPlacement {
|
||||
if c.SessionPlacement != "path" && c.SessionKey == "" {
|
||||
switch c.SessionPlacement {
|
||||
case "cookie", "query":
|
||||
c.SessionIDKey = "x_session"
|
||||
c.SessionKey = "x_session"
|
||||
case "header":
|
||||
c.SessionIDKey = "X-Session"
|
||||
}
|
||||
}
|
||||
|
||||
if c.SessionIDTable != "" {
|
||||
if predefined, ok := splithttp.PredefinedTable[c.SessionIDTable]; ok {
|
||||
c.SessionIDTable = predefined
|
||||
}
|
||||
room := roomSize(len(c.SessionIDTable), c.SessionIDLength.From, c.SessionIDLength.To)
|
||||
// 2.1B possiblities should be enough
|
||||
if room.Cmp(big.NewInt(2<<30)) < 0 {
|
||||
return nil, errors.New("sessionIDTable or sessionIDLength is too small")
|
||||
}
|
||||
if c.SessionIDLength.From <= 0 {
|
||||
return nil, errors.New("sessionIDLength.from must be greater than 0")
|
||||
}
|
||||
for i := 0; i < len(c.SessionIDTable); i++ {
|
||||
if c.SessionIDTable[i] >= 0x80 {
|
||||
return nil, errors.New("sessionIDTable must contain only ASCII characters")
|
||||
}
|
||||
c.SessionKey = "X-Session"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -424,9 +402,9 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
|
||||
XPaddingPlacement: c.XPaddingPlacement,
|
||||
XPaddingMethod: c.XPaddingMethod,
|
||||
UplinkHTTPMethod: c.UplinkHTTPMethod,
|
||||
SessionIDPlacement: c.SessionIDPlacement,
|
||||
SessionPlacement: c.SessionPlacement,
|
||||
SeqPlacement: c.SeqPlacement,
|
||||
SessionIDKey: c.SessionIDKey,
|
||||
SessionKey: c.SessionKey,
|
||||
SeqKey: c.SeqKey,
|
||||
UplinkDataPlacement: c.UplinkDataPlacement,
|
||||
UplinkDataKey: c.UplinkDataKey,
|
||||
@@ -438,8 +416,6 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
|
||||
ScMaxBufferedPosts: c.ScMaxBufferedPosts,
|
||||
ScStreamUpServerSecs: newRangeConfig(c.ScStreamUpServerSecs),
|
||||
ServerMaxHeaderBytes: c.ServerMaxHeaderBytes,
|
||||
SessionIDTable: c.SessionIDTable,
|
||||
SessionIDLength: newRangeConfig(c.SessionIDLength),
|
||||
Xmux: &splithttp.XmuxConfig{
|
||||
MaxConcurrency: newRangeConfig(c.Xmux.MaxConcurrency),
|
||||
MaxConnections: newRangeConfig(c.Xmux.MaxConnections),
|
||||
@@ -463,17 +439,6 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func roomSize(tableSize int, min, max int32) *big.Int {
|
||||
base := big.NewInt(int64(tableSize))
|
||||
sum := new(big.Int)
|
||||
term := new(big.Int)
|
||||
for k := min; k <= max; k++ {
|
||||
term.Exp(base, big.NewInt(int64(k)), nil)
|
||||
sum.Add(sum, term)
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
const (
|
||||
Byte = 1
|
||||
Kilobyte = 1024 * Byte
|
||||
@@ -1409,12 +1374,10 @@ func (c *HeaderCustomTCP) Build() (proto.Message, error) {
|
||||
}
|
||||
|
||||
type FragmentMask struct {
|
||||
Packets string `json:"packets"`
|
||||
Length Int32Range `json:"length"`
|
||||
Delay Int32Range `json:"delay"`
|
||||
Lengths []Int32Range `json:"lengths"`
|
||||
Delays []Int32Range `json:"delays"`
|
||||
MaxSplit Int32Range `json:"maxSplit"`
|
||||
Packets string `json:"packets"`
|
||||
Length Int32Range `json:"length"`
|
||||
Delay Int32Range `json:"delay"`
|
||||
MaxSplit Int32Range `json:"maxSplit"`
|
||||
}
|
||||
|
||||
func (c *FragmentMask) Build() (proto.Message, error) {
|
||||
@@ -1439,29 +1402,14 @@ func (c *FragmentMask) Build() (proto.Message, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if len(c.Lengths) > 0 {
|
||||
for _, r := range c.Lengths {
|
||||
config.LengthsMin = append(config.LengthsMin, int64(r.From))
|
||||
config.LengthsMax = append(config.LengthsMax, int64(r.To))
|
||||
}
|
||||
} else {
|
||||
config.LengthsMin = append(config.LengthsMin, int64(c.Length.From))
|
||||
config.LengthsMax = append(config.LengthsMax, int64(c.Length.To))
|
||||
config.LengthMin = int64(c.Length.From)
|
||||
config.LengthMax = int64(c.Length.To)
|
||||
if config.LengthMin == 0 {
|
||||
return nil, errors.New("LengthMin can't be 0")
|
||||
}
|
||||
|
||||
if config.LengthsMin[len(config.LengthsMin)-1] == 0 {
|
||||
return nil, errors.New("last lengths entry min can't be 0")
|
||||
}
|
||||
|
||||
if len(c.Delays) > 0 {
|
||||
for _, r := range c.Delays {
|
||||
config.DelaysMin = append(config.DelaysMin, int64(r.From))
|
||||
config.DelaysMax = append(config.DelaysMax, int64(r.To))
|
||||
}
|
||||
} else {
|
||||
config.DelaysMin = append(config.DelaysMin, int64(c.Delay.From))
|
||||
config.DelaysMax = append(config.DelaysMax, int64(c.Delay.To))
|
||||
}
|
||||
config.DelayMin = int64(c.Delay.From)
|
||||
config.DelayMax = int64(c.Delay.To)
|
||||
|
||||
config.MaxSplitMin = int64(c.MaxSplit.From)
|
||||
config.MaxSplitMax = int64(c.MaxSplit.To)
|
||||
@@ -1805,15 +1753,12 @@ func (c *MkcpLegacy) Build() (proto.Message, error) {
|
||||
}
|
||||
|
||||
type Salamander struct {
|
||||
Password string `json:"password"`
|
||||
PacketSize Int32Range `json:"packetSize"`
|
||||
Password string `json:"password"`
|
||||
PacketSize *Int32Range `json:"packetSize"`
|
||||
}
|
||||
|
||||
func (c *Salamander) Build() (proto.Message, error) {
|
||||
if c.PacketSize.To > 0 {
|
||||
if c.PacketSize.From <= 0 || c.PacketSize.To > 2048 {
|
||||
return nil, errors.New("gecko: invalid min/max packet size")
|
||||
}
|
||||
if c.PacketSize != nil {
|
||||
return &salamander.GeckoConfig{
|
||||
Password: c.Password,
|
||||
MinPacketSize: c.PacketSize.From,
|
||||
|
||||
@@ -3,7 +3,6 @@ package conf
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
@@ -38,9 +37,8 @@ func (c *WireGuardPeerConfig) Build() (proto.Message, error) {
|
||||
}
|
||||
|
||||
config.Endpoint = c.Endpoint
|
||||
if c.KeepAlive != 0 {
|
||||
config.KeepAlive = strconv.FormatUint(uint64(c.KeepAlive), 10)
|
||||
}
|
||||
// default 0
|
||||
config.KeepAlive = c.KeepAlive
|
||||
if c.AllowedIPs == nil {
|
||||
config.AllowedIps = []string{"0.0.0.0/0", "::0/0"}
|
||||
} else {
|
||||
@@ -58,6 +56,7 @@ type WireGuardConfig struct {
|
||||
Address []string `json:"address"`
|
||||
Peers []*WireGuardPeerConfig `json:"peers"`
|
||||
MTU int32 `json:"mtu"`
|
||||
NumWorkers int32 `json:"workers"`
|
||||
Reserved []byte `json:"reserved"`
|
||||
DomainStrategy string `json:"domainStrategy"`
|
||||
}
|
||||
@@ -94,6 +93,9 @@ func (c *WireGuardConfig) Build() (proto.Message, error) {
|
||||
} else {
|
||||
config.Mtu = c.MTU
|
||||
}
|
||||
// these a fallback code exists in wireguard-go code,
|
||||
// we don't need to process fallback manually
|
||||
config.NumWorkers = c.NumWorkers
|
||||
|
||||
if len(c.Reserved) != 0 && len(c.Reserved) != 3 {
|
||||
return nil, errors.New(`"reserved" should be empty or 3 bytes`)
|
||||
|
||||
@@ -38,10 +38,12 @@ func TestWireGuardConfig(t *testing.T) {
|
||||
// also can read from hex form directly
|
||||
PublicKey: "6e65ce0be17517110c17d77288ad87e7fd5252dcc7d09b95a39d61db03df832a",
|
||||
Endpoint: "127.0.0.1:1234",
|
||||
KeepAlive: 0,
|
||||
AllowedIps: []string{"0.0.0.0/0", "::0/0"},
|
||||
},
|
||||
},
|
||||
Mtu: 1300,
|
||||
NumWorkers: 2,
|
||||
DomainStrategy: wireguard.DeviceConfig_FORCE_IP64,
|
||||
NoKernelTun: false,
|
||||
},
|
||||
|
||||
@@ -173,6 +173,27 @@ func (c *InboundDetourConfig) Build() (*core.InboundHandlerConfig, error) {
|
||||
return nil, err
|
||||
}
|
||||
receiverSettings.StreamSettings = ss
|
||||
// TODO: Actually implement this breaking change
|
||||
protocol := ss.GetEffectiveProtocol()
|
||||
if (protocol == "websocket" || protocol == "httpupgrade" || protocol == "splithttp") &&
|
||||
(c.StreamSetting.SocketSettings == nil || len(c.StreamSetting.SocketSettings.TrustedXForwardedFor) == 0) {
|
||||
errors.LogWarning(
|
||||
context.Background(),
|
||||
`====== SECURITY WARNING ======`,
|
||||
"\n",
|
||||
`inbound "`, c.Tag, `" using `, protocol, ` has not configured "sockopt.trustedXForwardedFor".`,
|
||||
"\n",
|
||||
`THIS IS VERY INSECURE!!!`,
|
||||
"\n",
|
||||
`For compatibility, Xray still allows this for now and still trusts X-Forwarded-For implicitly.`,
|
||||
"\n",
|
||||
`Please configure "sockopt.trustedXForwardedFor" immediately.`,
|
||||
"\n",
|
||||
`In future versions, this option must be explicitly set.`,
|
||||
"\n",
|
||||
`====== SECURITY WARNING ======`,
|
||||
)
|
||||
}
|
||||
if strings.Contains(ss.SecurityType, "reality") && (receiverSettings.PortList == nil ||
|
||||
len(receiverSettings.PortList.Ports()) != 1 || receiverSettings.PortList.Ports()[0] != 443) {
|
||||
errors.LogWarning(context.Background(), `REALITY: Listening on non-443 ports may get your IP blocked by the GFW`)
|
||||
|
||||
@@ -29,13 +29,6 @@ type Client struct {
|
||||
}
|
||||
|
||||
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.(*hysteria.Config); !ok {
|
||||
return nil, errors.New("not hysteria transport")
|
||||
}
|
||||
if config.Server == nil {
|
||||
return nil, errors.New(`no target server found`)
|
||||
}
|
||||
@@ -44,10 +37,12 @@ func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) {
|
||||
return nil, errors.New("failed to get server spec").Base(err)
|
||||
}
|
||||
|
||||
return &Client{
|
||||
v := core.MustFromContext(ctx)
|
||||
client := &Client{
|
||||
server: server,
|
||||
policyManager: p,
|
||||
}, nil
|
||||
policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (c *Client) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error {
|
||||
|
||||
@@ -16,7 +16,6 @@ import (
|
||||
"github.com/xtls/xray-core/features/routing"
|
||||
"github.com/xtls/xray-core/proxy/hysteria/account"
|
||||
"github.com/xtls/xray-core/transport"
|
||||
"github.com/xtls/xray-core/transport/internet"
|
||||
"github.com/xtls/xray-core/transport/internet/hysteria"
|
||||
"github.com/xtls/xray-core/transport/internet/stat"
|
||||
)
|
||||
@@ -28,14 +27,6 @@ type Server struct {
|
||||
}
|
||||
|
||||
func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {
|
||||
v := core.MustFromContext(ctx)
|
||||
p := v.GetFeature(policy.ManagerType()).(policy.Manager)
|
||||
|
||||
streamSettings := session.StreamSettingsFromContext(ctx).(*internet.MemoryStreamConfig)
|
||||
if _, ok := streamSettings.ProtocolSettings.(*hysteria.Config); !ok {
|
||||
return nil, errors.New("not hysteria transport")
|
||||
}
|
||||
|
||||
validator := account.NewValidator()
|
||||
for _, user := range config.Users {
|
||||
u, err := user.ToMemoryUser()
|
||||
@@ -48,11 +39,14 @@ func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {
|
||||
}
|
||||
}
|
||||
|
||||
return &Server{
|
||||
v := core.MustFromContext(ctx)
|
||||
s := &Server{
|
||||
config: config,
|
||||
validator: validator,
|
||||
policyManager: p,
|
||||
}, nil
|
||||
policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *Server) HysteriaInboundValidator() *account.Validator {
|
||||
|
||||
+10
-22
@@ -7,7 +7,6 @@
|
||||
package loopback
|
||||
|
||||
import (
|
||||
proxyman "github.com/xtls/xray-core/app/proxyman"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
@@ -23,9 +22,8 @@ const (
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
InboundTag string `protobuf:"bytes,1,opt,name=inbound_tag,json=inboundTag,proto3" json:"inbound_tag,omitempty"`
|
||||
Sniffing *proxyman.SniffingConfig `protobuf:"bytes,2,opt,name=sniffing,proto3" json:"sniffing,omitempty"`
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
InboundTag string `protobuf:"bytes,1,opt,name=inbound_tag,json=inboundTag,proto3" json:"inbound_tag,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@@ -67,22 +65,14 @@ func (x *Config) GetInboundTag() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Config) GetSniffing() *proxyman.SniffingConfig {
|
||||
if x != nil {
|
||||
return x.Sniffing
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_proxy_loopback_config_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_proxy_loopback_config_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\x1bproxy/loopback/config.proto\x12\x13xray.proxy.loopback\x1a\x19app/proxyman/config.proto\"h\n" +
|
||||
"\x1bproxy/loopback/config.proto\x12\x13xray.proxy.loopback\")\n" +
|
||||
"\x06Config\x12\x1f\n" +
|
||||
"\vinbound_tag\x18\x01 \x01(\tR\n" +
|
||||
"inboundTag\x12=\n" +
|
||||
"\bsniffing\x18\x02 \x01(\v2!.xray.app.proxyman.SniffingConfigR\bsniffingB[\n" +
|
||||
"inboundTagB[\n" +
|
||||
"\x17com.xray.proxy.loopbackP\x01Z(github.com/xtls/xray-core/proxy/loopback\xaa\x02\x13Xray.Proxy.Loopbackb\x06proto3"
|
||||
|
||||
var (
|
||||
@@ -99,16 +89,14 @@ func file_proxy_loopback_config_proto_rawDescGZIP() []byte {
|
||||
|
||||
var file_proxy_loopback_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||
var file_proxy_loopback_config_proto_goTypes = []any{
|
||||
(*Config)(nil), // 0: xray.proxy.loopback.Config
|
||||
(*proxyman.SniffingConfig)(nil), // 1: xray.app.proxyman.SniffingConfig
|
||||
(*Config)(nil), // 0: xray.proxy.loopback.Config
|
||||
}
|
||||
var file_proxy_loopback_config_proto_depIdxs = []int32{
|
||||
1, // 0: xray.proxy.loopback.Config.sniffing:type_name -> xray.app.proxyman.SniffingConfig
|
||||
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
|
||||
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_proxy_loopback_config_proto_init() }
|
||||
|
||||
@@ -6,9 +6,6 @@ option go_package = "github.com/xtls/xray-core/proxy/loopback";
|
||||
option java_package = "com.xray.proxy.loopback";
|
||||
option java_multiple_files = true;
|
||||
|
||||
import "app/proxyman/config.proto";
|
||||
|
||||
message Config {
|
||||
string inbound_tag = 1;
|
||||
xray.app.proxyman.SniffingConfig sniffing = 2;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package loopback
|
||||
import (
|
||||
"context"
|
||||
|
||||
proxyman "github.com/xtls/xray-core/app/proxyman"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/session"
|
||||
@@ -14,8 +13,7 @@ import (
|
||||
)
|
||||
|
||||
type Loopback struct {
|
||||
inboundTag string
|
||||
sniffingRequest session.SniffingRequest
|
||||
config *Config
|
||||
dispatcherInstance routing.Dispatcher
|
||||
}
|
||||
|
||||
@@ -31,7 +29,6 @@ func (l *Loopback) Process(ctx context.Context, link *transport.Link, _ internet
|
||||
errors.LogInfo(ctx, "opening connection to ", destination)
|
||||
content := new(session.Content)
|
||||
content.SkipDNSResolve = true
|
||||
content.SniffingRequest = l.sniffingRequest
|
||||
|
||||
ctx = session.ContextWithContent(ctx, content)
|
||||
inbound := &session.Inbound{}
|
||||
@@ -40,26 +37,20 @@ func (l *Loopback) Process(ctx context.Context, link *transport.Link, _ internet
|
||||
// get a shallow copy to avoid modifying the inbound tag in upstream context
|
||||
*inbound = *originInbound
|
||||
}
|
||||
inbound.Tag = l.inboundTag
|
||||
inbound.Tag = l.config.InboundTag
|
||||
ctx = session.ContextWithInbound(ctx, inbound)
|
||||
|
||||
err := l.dispatcherInstance.DispatchLink(ctx, destination, link)
|
||||
if err != nil {
|
||||
return errors.New(ctx, "failed to process loopback connection").Base(err)
|
||||
errors.New(ctx, "failed to process loopback connection").Base(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Loopback) init(config *Config, dispatcherInstance routing.Dispatcher) error {
|
||||
l.dispatcherInstance = dispatcherInstance
|
||||
l.inboundTag = config.InboundTag
|
||||
if config.Sniffing.GetEnabled() {
|
||||
request, err := proxyman.BuildSniffingRequest(config.Sniffing)
|
||||
if err != nil {
|
||||
return errors.New("failed to build loopback sniffing request").Base(err).AtError()
|
||||
}
|
||||
l.sniffingRequest = request
|
||||
}
|
||||
l.config = config
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -356,13 +356,15 @@ func (x *Account) GetKey() string {
|
||||
}
|
||||
|
||||
type ClientConfig struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Address *net.IPOrDomain `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
|
||||
Port uint32 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"`
|
||||
Method string `protobuf:"bytes,3,opt,name=method,proto3" json:"method,omitempty"`
|
||||
Key string `protobuf:"bytes,4,opt,name=key,proto3" json:"key,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Address *net.IPOrDomain `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
|
||||
Port uint32 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"`
|
||||
Method string `protobuf:"bytes,3,opt,name=method,proto3" json:"method,omitempty"`
|
||||
Key string `protobuf:"bytes,4,opt,name=key,proto3" json:"key,omitempty"`
|
||||
UdpOverTcp bool `protobuf:"varint,5,opt,name=udp_over_tcp,json=udpOverTcp,proto3" json:"udp_over_tcp,omitempty"`
|
||||
UdpOverTcpVersion uint32 `protobuf:"varint,6,opt,name=udp_over_tcp_version,json=udpOverTcpVersion,proto3" json:"udp_over_tcp_version,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ClientConfig) Reset() {
|
||||
@@ -423,6 +425,20 @@ func (x *ClientConfig) GetKey() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ClientConfig) GetUdpOverTcp() bool {
|
||||
if x != nil {
|
||||
return x.UdpOverTcp
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *ClientConfig) GetUdpOverTcpVersion() uint32 {
|
||||
if x != nil {
|
||||
return x.UdpOverTcpVersion
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
var File_proxy_shadowsocks_2022_config_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_proxy_shadowsocks_2022_config_proto_rawDesc = "" +
|
||||
@@ -451,12 +467,15 @@ const file_proxy_shadowsocks_2022_config_proto_rawDesc = "" +
|
||||
"\fdestinations\x18\x03 \x03(\v2-.xray.proxy.shadowsocks_2022.RelayDestinationR\fdestinations\x122\n" +
|
||||
"\anetwork\x18\x04 \x03(\x0e2\x18.xray.common.net.NetworkR\anetwork\"\x1b\n" +
|
||||
"\aAccount\x12\x10\n" +
|
||||
"\x03key\x18\x01 \x01(\tR\x03key\"\x83\x01\n" +
|
||||
"\x03key\x18\x01 \x01(\tR\x03key\"\xd6\x01\n" +
|
||||
"\fClientConfig\x125\n" +
|
||||
"\aaddress\x18\x01 \x01(\v2\x1b.xray.common.net.IPOrDomainR\aaddress\x12\x12\n" +
|
||||
"\x04port\x18\x02 \x01(\rR\x04port\x12\x16\n" +
|
||||
"\x06method\x18\x03 \x01(\tR\x06method\x12\x10\n" +
|
||||
"\x03key\x18\x04 \x01(\tR\x03keyBr\n" +
|
||||
"\x03key\x18\x04 \x01(\tR\x03key\x12 \n" +
|
||||
"\fudp_over_tcp\x18\x05 \x01(\bR\n" +
|
||||
"udpOverTcp\x12/\n" +
|
||||
"\x14udp_over_tcp_version\x18\x06 \x01(\rR\x11udpOverTcpVersionBr\n" +
|
||||
"\x1fcom.xray.proxy.shadowsocks_2022P\x01Z0github.com/xtls/xray-core/proxy/shadowsocks_2022\xaa\x02\x1aXray.Proxy.Shadowsocks2022b\x06proto3"
|
||||
|
||||
var (
|
||||
|
||||
@@ -49,4 +49,6 @@ message ClientConfig {
|
||||
uint32 port = 2;
|
||||
string method = 3;
|
||||
string key = 4;
|
||||
bool udp_over_tcp = 5;
|
||||
uint32 udp_over_tcp_version = 6;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
B "github.com/sagernet/sing/common/buf"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/common/uot"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
@@ -28,9 +29,10 @@ func init() {
|
||||
}
|
||||
|
||||
type Outbound struct {
|
||||
ctx context.Context
|
||||
server net.Destination
|
||||
method shadowsocks.Method
|
||||
ctx context.Context
|
||||
server net.Destination
|
||||
method shadowsocks.Method
|
||||
uotClient *uot.Client
|
||||
}
|
||||
|
||||
func NewClient(ctx context.Context, config *ClientConfig) (*Outbound, error) {
|
||||
@@ -54,6 +56,9 @@ func NewClient(ctx context.Context, config *ClientConfig) (*Outbound, error) {
|
||||
} else {
|
||||
return nil, errors.New("unknown method ", config.Method)
|
||||
}
|
||||
if config.UdpOverTcp {
|
||||
o.uotClient = &uot.Client{Version: uint8(config.UdpOverTcpVersion)}
|
||||
}
|
||||
return o, nil
|
||||
}
|
||||
|
||||
@@ -77,7 +82,11 @@ func (o *Outbound) Process(ctx context.Context, link *transport.Link, dialer int
|
||||
errors.LogInfo(ctx, "tunneling request to ", destination, " via ", o.server.NetAddr())
|
||||
|
||||
serverDestination := o.server
|
||||
serverDestination.Network = network
|
||||
if o.uotClient != nil {
|
||||
serverDestination.Network = net.Network_TCP
|
||||
} else {
|
||||
serverDestination.Network = network
|
||||
}
|
||||
connection, err := dialer.Dial(ctx, serverDestination)
|
||||
if err != nil {
|
||||
return errors.New("failed to connect to server").Base(err)
|
||||
@@ -140,7 +149,15 @@ func (o *Outbound) Process(ctx context.Context, link *transport.Link, dialer int
|
||||
}
|
||||
}
|
||||
|
||||
serverConn := o.method.DialPacketConn(connection)
|
||||
return singbridge.ReturnError(bufio.CopyPacketConn(ctx, packetConn, serverConn))
|
||||
if o.uotClient != nil {
|
||||
uConn, err := o.uotClient.DialEarlyConn(o.method.DialEarlyConn(connection, uot.RequestDestination(o.uotClient.Version)), false, singbridge.ToSocksaddr(destination))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return singbridge.ReturnError(bufio.CopyPacketConn(ctx, packetConn, uConn))
|
||||
} else {
|
||||
serverConn := o.method.DialPacketConn(connection)
|
||||
return singbridge.ReturnError(bufio.CopyPacketConn(ctx, packetConn, serverConn))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,12 +209,11 @@ func (s *ServerSession) handshake5(nMethod byte, reader io.Reader, writer net.Co
|
||||
}
|
||||
responsePort = net.Port(udpHub.LocalAddr().(*net.UDPAddr).Port)
|
||||
expectedRemote := &gonet.UDPAddr{}
|
||||
// UDP Associate should not specify a domain as source IP
|
||||
if request.Address.Family().IsDomain() || request.Address.IP().IsUnspecified() {
|
||||
if request.Address.IP().IsUnspecified() {
|
||||
expectedRemote.IP = writer.RemoteAddr().(*net.TCPAddr).IP // unix?
|
||||
} else {
|
||||
expectedRemote.IP = request.Address.IP()
|
||||
expectedRemote.Port = int(request.Port) // 0 is allowed
|
||||
expectedRemote.IP = request.Address.IP() // panic?
|
||||
expectedRemote.Port = int(request.Port) // 0 is allowed
|
||||
}
|
||||
tempUDPConn = NewTempUDPConn(udpHub, writer, expectedRemote)
|
||||
}
|
||||
|
||||
+3
-13
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
goerrors "errors"
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
@@ -217,27 +216,18 @@ func (s *Server) handleUDPPayload(ctx context.Context, conn stat.Connection, dis
|
||||
defer udpServer.RemoveRay()
|
||||
|
||||
inbound := session.InboundFromContext(ctx)
|
||||
if inbound != nil && inbound.Source.IsValid() {
|
||||
errors.LogInfo(ctx, "client UDP connection from ", inbound.Source)
|
||||
}
|
||||
|
||||
var dest *net.Destination
|
||||
|
||||
reader := buf.NewPacketReader(conn)
|
||||
var changeRemote sync.Once
|
||||
for {
|
||||
mpayload, err := reader.ReadMultiBuffer()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
changeRemote.Do(func() {
|
||||
if inbound != nil {
|
||||
newInbound := *inbound
|
||||
// change source to real remote UDP address
|
||||
newInbound.Source = net.DestinationFromAddr(conn.RemoteAddr())
|
||||
newInbound.Local = net.DestinationFromAddr(conn.LocalAddr())
|
||||
inbound = &newInbound
|
||||
ctx = session.ContextWithInbound(ctx, inbound)
|
||||
errors.LogInfo(ctx, "client UDP connection from ", inbound.Source)
|
||||
}
|
||||
})
|
||||
|
||||
for _, payload := range mpayload {
|
||||
request, err := DecodeUDPPacket(payload)
|
||||
|
||||
@@ -42,11 +42,10 @@ type ConnectionHandler interface {
|
||||
// Handler implements ConnectionHandler
|
||||
var _ ConnectionHandler = (*Handler)(nil)
|
||||
|
||||
// Handler implements common.Runnable
|
||||
var _ common.Runnable = (*Handler)(nil)
|
||||
|
||||
// Init the Handler instance with necessary parameters
|
||||
func (t *Handler) Init(ctx context.Context, pm policy.Manager, dispatcher routing.Dispatcher) error {
|
||||
var err error
|
||||
|
||||
// Retrieve tag and sniffing config from context (set by AlwaysOnInboundHandler)
|
||||
if inbound := session.InboundFromContext(ctx); inbound != nil {
|
||||
t.tag = inbound.Tag
|
||||
@@ -59,10 +58,6 @@ func (t *Handler) Init(ctx context.Context, pm policy.Manager, dispatcher routin
|
||||
t.policyManager = pm
|
||||
t.dispatcher = dispatcher
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Handler) Start() error {
|
||||
tunName := t.config.Name
|
||||
tunInterface, err := NewTun(t.config)
|
||||
if err != nil {
|
||||
@@ -104,7 +99,7 @@ func (t *Handler) Start() error {
|
||||
|
||||
tunStackOptions := StackOptions{
|
||||
Tun: tunInterface,
|
||||
IdleTimeout: t.policyManager.ForLevel(t.config.UserLevel).Timeouts.ConnectionIdle,
|
||||
IdleTimeout: pm.ForLevel(t.config.UserLevel).Timeouts.ConnectionIdle,
|
||||
}
|
||||
tunStack, err := NewStack(t.ctx, tunStackOptions, t)
|
||||
if err != nil {
|
||||
@@ -179,7 +174,7 @@ func (t *Handler) HandleConnection(conn net.Conn, destination net.Destination) {
|
||||
|
||||
// Close implements common.Closable.
|
||||
func (t *Handler) Close() error {
|
||||
return errors.Combine(common.CloseIfExists(t.stack), common.CloseIfExists(t.tun))
|
||||
return errors.Combine(t.stack.Close(), t.tun.Close())
|
||||
}
|
||||
|
||||
// Network implements proxy.Inbound
|
||||
|
||||
+3
-73
@@ -4,11 +4,8 @@ package tun
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/platform"
|
||||
"golang.org/x/sys/unix"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/link/fdbased"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
@@ -21,7 +18,6 @@ type LinuxTun struct {
|
||||
tunFd int
|
||||
tunLink netlink.Link
|
||||
options *Config
|
||||
ownsTun bool
|
||||
}
|
||||
|
||||
// LinuxTun implements Tun
|
||||
@@ -29,24 +25,12 @@ var _ Tun = (*LinuxTun)(nil)
|
||||
|
||||
// NewTun builds new tun interface handler (linux specific)
|
||||
func NewTun(options *Config) (Tun, error) {
|
||||
tunFd, tunLink, fdProvided, err := openFromEnv(options.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if fdProvided {
|
||||
return &LinuxTun{
|
||||
tunFd: tunFd,
|
||||
tunLink: tunLink,
|
||||
options: options,
|
||||
}, nil
|
||||
}
|
||||
|
||||
tunFd, err = open(options.Name)
|
||||
tunFd, err := open(options.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tunLink, err = setup(options.Name, int(options.MTU))
|
||||
tunLink, err := setup(options.Name, int(options.MTU))
|
||||
if err != nil {
|
||||
_ = unix.Close(tunFd)
|
||||
return nil, err
|
||||
@@ -56,59 +40,11 @@ func NewTun(options *Config) (Tun, error) {
|
||||
tunFd: tunFd,
|
||||
tunLink: tunLink,
|
||||
options: options,
|
||||
ownsTun: true,
|
||||
}
|
||||
|
||||
return linuxTun, nil
|
||||
}
|
||||
|
||||
func openFromEnv(expectedName string) (int, netlink.Link, bool, error) {
|
||||
fdStr := platform.NewEnvFlag(platform.TunFdKey).GetValue(func() string { return "" })
|
||||
if fdStr == "" {
|
||||
return -1, nil, false, nil
|
||||
}
|
||||
|
||||
fd, err := strconv.Atoi(fdStr)
|
||||
if err != nil {
|
||||
return -1, nil, true, errors.New("invalid ", platform.TunFdKey).Base(err)
|
||||
}
|
||||
if fd < 3 {
|
||||
return -1, nil, true, errors.New("invalid ", platform.TunFdKey, ": file descriptor must be >= 3")
|
||||
}
|
||||
|
||||
ifr, err := unix.NewIfreq("")
|
||||
if err != nil {
|
||||
return -1, nil, true, err
|
||||
}
|
||||
if err = unix.IoctlIfreq(fd, unix.TUNGETIFF, ifr); err != nil {
|
||||
return -1, nil, true, err
|
||||
}
|
||||
|
||||
flags := ifr.Uint16()
|
||||
if flags&unix.IFF_TUN == 0 {
|
||||
return -1, nil, true, errors.New("invalid ", platform.TunFdKey, ": file descriptor is not a TUN device")
|
||||
}
|
||||
if flags&unix.IFF_NO_PI == 0 {
|
||||
return -1, nil, true, errors.New("invalid ", platform.TunFdKey, ": TUN device must use IFF_NO_PI")
|
||||
}
|
||||
|
||||
actualName := ifr.Name()
|
||||
if expectedName != "" && actualName != expectedName {
|
||||
return -1, nil, true, errors.New("invalid ", platform.TunFdKey, ": TUN device name ", actualName, " does not match configured name ", expectedName)
|
||||
}
|
||||
|
||||
tunLink, err := netlink.LinkByName(actualName)
|
||||
if err != nil {
|
||||
return -1, nil, true, err
|
||||
}
|
||||
|
||||
if err = unix.SetNonblock(fd, true); err != nil {
|
||||
return -1, nil, true, err
|
||||
}
|
||||
|
||||
return fd, tunLink, true, nil
|
||||
}
|
||||
|
||||
// open the file that implements tun interface in the OS
|
||||
func open(name string) (int, error) {
|
||||
fd, err := unix.Open("/dev/net/tun", unix.O_RDWR, 0)
|
||||
@@ -157,10 +93,6 @@ func setup(name string, MTU int) (netlink.Link, error) {
|
||||
|
||||
// Start is called by handler to bring tun interface to life
|
||||
func (t *LinuxTun) Start() error {
|
||||
if !t.ownsTun {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := netlink.LinkSetUp(t.tunLink)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -171,9 +103,7 @@ func (t *LinuxTun) Start() error {
|
||||
|
||||
// Close is called to shut down the tun interface
|
||||
func (t *LinuxTun) Close() error {
|
||||
if t.ownsTun {
|
||||
_ = netlink.LinkSetDown(t.tunLink)
|
||||
}
|
||||
_ = netlink.LinkSetDown(t.tunLink)
|
||||
_ = unix.Close(t.tunFd)
|
||||
|
||||
return nil
|
||||
|
||||
+12
-20
@@ -98,22 +98,18 @@ type udpConn struct {
|
||||
}
|
||||
|
||||
func (c *udpConn) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
||||
for {
|
||||
e, ok := <-c.egress
|
||||
if !ok {
|
||||
return nil, io.EOF
|
||||
}
|
||||
|
||||
b := buf.New()
|
||||
if _, err := b.Write(e.data); err != nil {
|
||||
errors.LogErrorInner(context.Background(), err, "drop packet to ", e.dest, " with size ", len(e.data))
|
||||
b.Release()
|
||||
continue
|
||||
}
|
||||
b.UDP = e.dest
|
||||
|
||||
return buf.MultiBuffer{b}, nil
|
||||
e, ok := <-c.egress
|
||||
if !ok {
|
||||
return nil, io.EOF
|
||||
}
|
||||
|
||||
b := buf.New()
|
||||
if _, err := b.Write(e.data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b.UDP = e.dest
|
||||
|
||||
return buf.MultiBuffer{b}, nil
|
||||
}
|
||||
|
||||
// Read packets from the connection
|
||||
@@ -133,11 +129,7 @@ func (c *udpConn) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
||||
for i, b := range mb {
|
||||
dst := c.dst
|
||||
if b.UDP != nil {
|
||||
if b.UDP.Address.Family().IsDomain() {
|
||||
errors.LogError(context.Background(), "impossible domain packet ", b.UDP, " reply via original target ", dst)
|
||||
} else {
|
||||
dst = *b.UDP
|
||||
}
|
||||
dst = *b.UDP
|
||||
}
|
||||
err := c.handler.writePacket(b.Bytes(), dst, c.src)
|
||||
if err != nil {
|
||||
|
||||
+238
-123
@@ -2,150 +2,265 @@ package wireguard
|
||||
|
||||
import (
|
||||
"context"
|
||||
goerrors "errors"
|
||||
"io"
|
||||
"net"
|
||||
gonet "net"
|
||||
"net/netip"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"golang.zx2c4.com/wireguard/conn"
|
||||
"golang.zx2c4.com/wireguard/device"
|
||||
|
||||
"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/features/dns"
|
||||
"github.com/xtls/xray-core/transport/internet"
|
||||
)
|
||||
|
||||
type bind struct {
|
||||
resolveFunc func(host string) (net.IP, error)
|
||||
listenFunc func() (net.PacketConn, error)
|
||||
downFunc func() error
|
||||
reserved []byte
|
||||
|
||||
net.PacketConn
|
||||
closeCh chan struct{}
|
||||
mu sync.Mutex
|
||||
type netReadInfo struct {
|
||||
buff *buf.Buffer
|
||||
endpoint conn.Endpoint
|
||||
}
|
||||
|
||||
func (b *bind) Open(port uint16) (fns []conn.ReceiveFunc, actualPort uint16, err error) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
// reduce duplicated code
|
||||
type netBind struct {
|
||||
dns dns.Client
|
||||
dnsOption dns.IPOption
|
||||
|
||||
if b.PacketConn != nil {
|
||||
return nil, 0, conn.ErrBindAlreadyOpen
|
||||
}
|
||||
workers int
|
||||
readQueue chan *netReadInfo
|
||||
closedCh chan struct{}
|
||||
}
|
||||
|
||||
c, err := b.listenFunc()
|
||||
// SetMark implements conn.Bind
|
||||
func (bind *netBind) SetMark(mark uint32) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseEndpoint implements conn.Bind
|
||||
func (n *netBind) ParseEndpoint(s string) (conn.Endpoint, error) {
|
||||
ipStr, port, err := net.SplitHostPort(s)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
return nil, err
|
||||
}
|
||||
b.PacketConn = c
|
||||
ch := make(chan struct{})
|
||||
b.closeCh = ch
|
||||
|
||||
return []conn.ReceiveFunc{
|
||||
func(bufs [][]byte, sizes []int, eps []conn.Endpoint) (n int, err error) {
|
||||
for {
|
||||
n, addr, err := c.ReadFrom(bufs[0])
|
||||
if err != nil {
|
||||
if goerrors.Is(err, io.EOF) || goerrors.Is(err, io.ErrClosedPipe) || goerrors.Is(err, net.ErrClosed) {
|
||||
select {
|
||||
case <-ch:
|
||||
default:
|
||||
errors.LogErrorInner(context.Background(), err, "unexpected closed")
|
||||
if b.downFunc != nil {
|
||||
go func() {
|
||||
common.Must(b.downFunc())
|
||||
}()
|
||||
}
|
||||
}
|
||||
return 0, net.ErrClosed
|
||||
}
|
||||
errors.LogErrorInner(context.Background(), err, "bind recv err")
|
||||
continue
|
||||
}
|
||||
if n > 3 {
|
||||
bufs[0][1] = 0
|
||||
bufs[0][2] = 0
|
||||
bufs[0][3] = 0
|
||||
}
|
||||
sizes[0] = n
|
||||
eps[0] = &conn.StdNetEndpoint{AddrPort: addr.(*net.UDPAddr).AddrPort()}
|
||||
return 1, nil
|
||||
}
|
||||
},
|
||||
}, uint16(c.LocalAddr().(*net.UDPAddr).Port), nil
|
||||
}
|
||||
|
||||
func (b *bind) Close() error {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
if b.PacketConn != nil {
|
||||
close(b.closeCh)
|
||||
_ = b.PacketConn.Close()
|
||||
b.PacketConn = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *bind) SetMark(mark uint32) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *bind) Send(bufs [][]byte, ep conn.Endpoint) (err error) {
|
||||
b.mu.Lock()
|
||||
c := b.PacketConn
|
||||
b.mu.Unlock()
|
||||
|
||||
if c == nil {
|
||||
return syscall.EAFNOSUPPORT
|
||||
portNum, err := strconv.Atoi(port)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := range bufs {
|
||||
if len(bufs[i]) > 3 && len(b.reserved) == 3 {
|
||||
bufs[i][1] = b.reserved[0]
|
||||
bufs[i][2] = b.reserved[1]
|
||||
bufs[i][3] = b.reserved[2]
|
||||
}
|
||||
_, err = c.WriteTo(bufs[i], net.UDPAddrFromAddrPort(ep.(*conn.StdNetEndpoint).AddrPort))
|
||||
if err != nil {
|
||||
errors.LogErrorInner(context.Background(), err, "bind send err")
|
||||
break
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (b *bind) ParseEndpoint(s string) (conn.Endpoint, error) {
|
||||
if b.resolveFunc == nil {
|
||||
e, err := netip.ParseAddrPort(s)
|
||||
addr := net.ParseAddress(ipStr)
|
||||
if addr.Family() == net.AddressFamilyDomain {
|
||||
ips, _, err := n.dns.LookupIP(addr.Domain(), n.dnsOption)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if len(ips) == 0 {
|
||||
return nil, dns.ErrEmptyResponse
|
||||
}
|
||||
return &conn.StdNetEndpoint{
|
||||
AddrPort: e,
|
||||
}, nil
|
||||
addr = net.IPAddress(ips[0])
|
||||
}
|
||||
host, sport, err := net.SplitHostPort(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
dst := net.Destination{
|
||||
Address: addr,
|
||||
Port: net.Port(portNum),
|
||||
Network: net.Network_UDP,
|
||||
}
|
||||
port, err := strconv.Atoi(sport)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if port < 0 || port > 65535 {
|
||||
return nil, errors.New("invalid port " + sport)
|
||||
}
|
||||
ip, err := b.resolveFunc(host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addr, _ := netip.AddrFromSlice(ip)
|
||||
return &conn.StdNetEndpoint{
|
||||
AddrPort: netip.AddrPortFrom(addr, uint16(port)),
|
||||
|
||||
return &netEndpoint{
|
||||
dst: dst,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *bind) BatchSize() int {
|
||||
// BatchSize implements conn.Bind
|
||||
func (bind *netBind) BatchSize() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
// Open implements conn.Bind
|
||||
func (bind *netBind) Open(uport uint16) ([]conn.ReceiveFunc, uint16, error) {
|
||||
bind.closedCh = make(chan struct{})
|
||||
errors.LogDebug(context.Background(), "bind opened")
|
||||
|
||||
fun := func(bufs [][]byte, sizes []int, eps []conn.Endpoint) (n int, err error) {
|
||||
select {
|
||||
case r := <-bind.readQueue:
|
||||
sizes[0], eps[0] = copy(bufs[0], r.buff.Bytes()), r.endpoint
|
||||
r.buff.Release()
|
||||
return 1, nil
|
||||
case <-bind.closedCh:
|
||||
errors.LogDebug(context.Background(), "recv func closed")
|
||||
return 0, gonet.ErrClosed
|
||||
}
|
||||
}
|
||||
workers := bind.workers
|
||||
if workers <= 0 {
|
||||
workers = runtime.NumCPU()
|
||||
}
|
||||
if workers <= 0 {
|
||||
workers = 1
|
||||
}
|
||||
arr := make([]conn.ReceiveFunc, workers)
|
||||
for i := 0; i < workers; i++ {
|
||||
arr[i] = fun
|
||||
}
|
||||
|
||||
return arr, uint16(uport), nil
|
||||
}
|
||||
|
||||
// Close implements conn.Bind
|
||||
func (bind *netBind) Close() error {
|
||||
errors.LogDebug(context.Background(), "bind closed")
|
||||
if bind.closedCh != nil {
|
||||
close(bind.closedCh)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type netBindClient struct {
|
||||
netBind
|
||||
|
||||
ctx context.Context
|
||||
dialer internet.Dialer
|
||||
reserved []byte
|
||||
}
|
||||
|
||||
func (bind *netBindClient) connectTo(endpoint *netEndpoint) error {
|
||||
c, err := bind.dialer.Dial(bind.ctx, endpoint.dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
endpoint.conn = c
|
||||
|
||||
go func() {
|
||||
for {
|
||||
buff := buf.NewWithSize(device.MaxMessageSize)
|
||||
n, err := buff.ReadFrom(c)
|
||||
if err != nil {
|
||||
buff.Release()
|
||||
endpoint.conn = nil
|
||||
c.Close()
|
||||
return
|
||||
}
|
||||
|
||||
rawBytes := buff.Bytes()
|
||||
if n > 3 {
|
||||
rawBytes[1] = 0
|
||||
rawBytes[2] = 0
|
||||
rawBytes[3] = 0
|
||||
}
|
||||
|
||||
select {
|
||||
case bind.readQueue <- &netReadInfo{
|
||||
buff: buff,
|
||||
endpoint: endpoint,
|
||||
}:
|
||||
case <-bind.closedCh:
|
||||
buff.Release()
|
||||
endpoint.conn = nil
|
||||
c.Close()
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bind *netBindClient) Send(buff [][]byte, endpoint conn.Endpoint) error {
|
||||
var err error
|
||||
|
||||
nend, ok := endpoint.(*netEndpoint)
|
||||
if !ok {
|
||||
return conn.ErrWrongEndpointType
|
||||
}
|
||||
|
||||
if nend.conn == nil {
|
||||
err = bind.connectTo(nend)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, buff := range buff {
|
||||
if len(buff) > 3 && len(bind.reserved) == 3 {
|
||||
copy(buff[1:], bind.reserved)
|
||||
}
|
||||
if _, err = nend.conn.Write(buff); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type netBindServer struct {
|
||||
netBind
|
||||
}
|
||||
|
||||
func (bind *netBindServer) Send(buff [][]byte, endpoint conn.Endpoint) error {
|
||||
var err error
|
||||
|
||||
nend, ok := endpoint.(*netEndpoint)
|
||||
if !ok {
|
||||
return conn.ErrWrongEndpointType
|
||||
}
|
||||
|
||||
if nend.conn == nil {
|
||||
errors.LogDebug(context.Background(), nend.dst.NetAddr(), " send on closed peer")
|
||||
return errors.New("peer closed")
|
||||
}
|
||||
|
||||
for _, buff := range buff {
|
||||
if _, err = nend.conn.Write(buff); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
type netEndpoint struct {
|
||||
dst net.Destination
|
||||
conn net.Conn
|
||||
}
|
||||
|
||||
func (netEndpoint) ClearSrc() {}
|
||||
|
||||
func (e netEndpoint) DstIP() netip.Addr {
|
||||
return netip.Addr{}
|
||||
}
|
||||
|
||||
func (e netEndpoint) SrcIP() netip.Addr {
|
||||
return netip.Addr{}
|
||||
}
|
||||
|
||||
func (e netEndpoint) DstToBytes() []byte {
|
||||
var dat []byte
|
||||
if e.dst.Address.Family().IsIPv4() {
|
||||
dat = e.dst.Address.IP().To4()[:]
|
||||
} else {
|
||||
dat = e.dst.Address.IP().To16()[:]
|
||||
}
|
||||
dat = append(dat, byte(e.dst.Port), byte(e.dst.Port>>8))
|
||||
return dat
|
||||
}
|
||||
|
||||
func (e netEndpoint) DstToString() string {
|
||||
return e.dst.NetAddr()
|
||||
}
|
||||
|
||||
func (e netEndpoint) SrcToString() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func toNetIpAddr(addr net.Address) netip.Addr {
|
||||
if addr.Family().IsIPv4() {
|
||||
ip := addr.IP()
|
||||
return netip.AddrFrom4([4]byte{ip[0], ip[1], ip[2], ip[3]})
|
||||
} else {
|
||||
ip := addr.IP()
|
||||
arr := [16]byte{}
|
||||
for i := 0; i < 16; i++ {
|
||||
arr[i] = ip[i]
|
||||
}
|
||||
return netip.AddrFrom16(arr)
|
||||
}
|
||||
}
|
||||
|
||||
+254
-355
@@ -1,135 +1,148 @@
|
||||
/*
|
||||
|
||||
Some of codes are copied from https://github.com/octeep/wireproxy, license below.
|
||||
|
||||
Copyright (c) 2022 Wind T.F. Wong <octeep@pm.me>
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
package wireguard
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
gonet "net"
|
||||
"net/netip"
|
||||
reflect "reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
"github.com/xtls/xray-core/common/dice"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/log"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/net/cnc"
|
||||
"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/dns"
|
||||
"github.com/xtls/xray-core/features/policy"
|
||||
"github.com/xtls/xray-core/features/stats"
|
||||
"github.com/xtls/xray-core/transport"
|
||||
"github.com/xtls/xray-core/transport/internet"
|
||||
"golang.zx2c4.com/wireguard/device"
|
||||
)
|
||||
|
||||
// Handler is an outbound connection that silently swallow the entire payload.
|
||||
type Handler struct {
|
||||
conf *DeviceConfig
|
||||
net Tunnel
|
||||
bind *netBindClient
|
||||
policyManager policy.Manager
|
||||
dns dns.Client
|
||||
|
||||
streamSettings *internet.MemoryStreamConfig
|
||||
uplinkCounter stats.Counter
|
||||
downlinkCounter stats.Counter
|
||||
|
||||
tun tun.Device
|
||||
tnet *Net
|
||||
dev *device.Device
|
||||
mu sync.Mutex
|
||||
// cached configuration
|
||||
endpoints []netip.Addr
|
||||
hasIPv4, hasIPv6 bool
|
||||
wgLock sync.Mutex
|
||||
}
|
||||
|
||||
func NewClient(ctx context.Context, conf *DeviceConfig) (*Handler, error) {
|
||||
// New creates a new wireguard handler.
|
||||
func New(ctx context.Context, conf *DeviceConfig) (*Handler, error) {
|
||||
v := core.MustFromContext(ctx)
|
||||
p := v.GetFeature(policy.ManagerType()).(policy.Manager)
|
||||
|
||||
endpoints, hasIPv4, hasIPv6, err := parseEndpoints(conf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d := v.GetFeature(dns.ClientType()).(dns.Client)
|
||||
|
||||
streamSettings := session.StreamSettingsFromContext(ctx).(*internet.MemoryStreamConfig)
|
||||
tag := session.FullHandlerFromContext(ctx).Tag()
|
||||
var uplinkCounter stats.Counter
|
||||
var downlinkCounter stats.Counter
|
||||
if len(tag) > 0 && p.ForSystem().Stats.OutboundUplink {
|
||||
statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager)
|
||||
name := "outbound>>>" + tag + ">>>traffic>>>uplink"
|
||||
c, _ := stats.GetOrRegisterCounter(statsManager, name)
|
||||
if c != nil {
|
||||
uplinkCounter = c
|
||||
}
|
||||
}
|
||||
if len(tag) > 0 && p.ForSystem().Stats.OutboundDownlink {
|
||||
statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager)
|
||||
name := "outbound>>>" + tag + ">>>traffic>>>downlink"
|
||||
c, _ := stats.GetOrRegisterCounter(statsManager, name)
|
||||
if c != nil {
|
||||
downlinkCounter = c
|
||||
}
|
||||
}
|
||||
|
||||
if len(conf.Peers) == 0 {
|
||||
return nil, errors.New("empty peers")
|
||||
}
|
||||
for _, peer := range conf.Peers {
|
||||
if peer.PublicKey == "" {
|
||||
return nil, errors.New("peer without publickey")
|
||||
}
|
||||
if peer.Endpoint == "" {
|
||||
return nil, errors.New("peer without endpoint")
|
||||
}
|
||||
}
|
||||
|
||||
localAddresses := make([]netip.Addr, 0, len(conf.Endpoint))
|
||||
for _, localaddress := range conf.Endpoint {
|
||||
addr, err := netip.ParseAddr(localaddress)
|
||||
if err == nil {
|
||||
localAddresses = append(localAddresses, addr)
|
||||
continue
|
||||
}
|
||||
prefix, err := netip.ParsePrefix(localaddress)
|
||||
if err == nil {
|
||||
localAddresses = append(localAddresses, prefix.Addr())
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
kernelTunSupported, err := KernelTunSupported()
|
||||
if err != nil {
|
||||
errors.LogWarningInner(context.Background(), err, "Failed to check kernel TUN support")
|
||||
}
|
||||
var tun tun.Device
|
||||
var tnet *Net
|
||||
if !conf.NoKernelTun && kernelTunSupported {
|
||||
errors.LogWarning(context.Background(), "Using kernel TUN")
|
||||
tun, tnet, err = createKernelTun(localAddresses, []netip.Addr{netip.MustParseAddr("1.1.1.1"), netip.MustParseAddr("1.0.0.1"), netip.MustParseAddr("2606:4700:4700::1111"), netip.MustParseAddr("2606:4700:4700::1001")}, int(conf.Mtu))
|
||||
} else {
|
||||
errors.LogWarning(context.Background(), "Using gVisor TUN")
|
||||
tun, tnet, _, err = CreateNetTUN(localAddresses, []netip.Addr{netip.MustParseAddr("1.1.1.1"), netip.MustParseAddr("1.0.0.1"), netip.MustParseAddr("2606:4700:4700::1111"), netip.MustParseAddr("2606:4700:4700::1001")}, int(conf.Mtu), true)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Handler{
|
||||
conf: conf,
|
||||
policyManager: p,
|
||||
policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
|
||||
dns: d,
|
||||
|
||||
streamSettings: streamSettings,
|
||||
uplinkCounter: uplinkCounter,
|
||||
downlinkCounter: downlinkCounter,
|
||||
|
||||
tun: tun,
|
||||
tnet: tnet,
|
||||
endpoints: endpoints,
|
||||
hasIPv4: hasIPv4,
|
||||
hasIPv6: hasIPv6,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Process implements proxy.Outbound.Process.
|
||||
func (h *Handler) Close() (err error) {
|
||||
go func() {
|
||||
h.wgLock.Lock()
|
||||
defer h.wgLock.Unlock()
|
||||
|
||||
if h.net != nil {
|
||||
_ = h.net.Close()
|
||||
h.net = nil
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Handler) processWireGuard(ctx context.Context, dialer internet.Dialer) (err error) {
|
||||
h.wgLock.Lock()
|
||||
defer h.wgLock.Unlock()
|
||||
|
||||
if h.bind != nil && h.bind.dialer == dialer && h.net != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Record(&log.GeneralMessage{
|
||||
Severity: log.Severity_Info,
|
||||
Content: "switching dialer",
|
||||
})
|
||||
|
||||
if h.net != nil {
|
||||
_ = h.net.Close()
|
||||
h.net = nil
|
||||
}
|
||||
if h.bind != nil {
|
||||
_ = h.bind.Close()
|
||||
h.bind = nil
|
||||
}
|
||||
|
||||
// bind := conn.NewStdNetBind() // TODO: conn.Bind wrapper for dialer
|
||||
h.bind = &netBindClient{
|
||||
netBind: netBind{
|
||||
dns: h.dns,
|
||||
dnsOption: dns.IPOption{
|
||||
IPv4Enable: h.hasIPv4,
|
||||
IPv6Enable: h.hasIPv6,
|
||||
},
|
||||
workers: int(h.conf.NumWorkers),
|
||||
readQueue: make(chan *netReadInfo),
|
||||
},
|
||||
ctx: ctx,
|
||||
dialer: dialer,
|
||||
reserved: h.conf.Reserved,
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
h.bind.Close()
|
||||
h.bind = nil
|
||||
}
|
||||
}()
|
||||
|
||||
h.net, err = h.makeVirtualTun()
|
||||
if err != nil {
|
||||
return errors.New("failed to create virtual tun interface").Base(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Process implements OutboundHandler.Dispatch().
|
||||
func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error {
|
||||
outbounds := session.OutboundsFromContext(ctx)
|
||||
ob := outbounds[len(outbounds)-1]
|
||||
@@ -139,31 +152,40 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
||||
ob.Name = "wireguard"
|
||||
ob.CanSpliceCopy = 3
|
||||
|
||||
if h.dev == nil {
|
||||
if err := h.init(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := h.dev.Up(); err != nil {
|
||||
if err := h.processWireGuard(ctx, dialer); err != nil {
|
||||
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())
|
||||
// Destination of the inner request.
|
||||
destination := ob.Target
|
||||
command := protocol.RequestCommandTCP
|
||||
if destination.Network == net.Network_UDP {
|
||||
command = protocol.RequestCommandUDP
|
||||
}
|
||||
|
||||
addrPort := netip.AddrPortFrom(addr, ob.Target.Port.Value())
|
||||
if !addrPort.IsValid() {
|
||||
return errors.New("invalid target ", ob.Target)
|
||||
// resolve dns
|
||||
addr := destination.Address
|
||||
if addr.Family().IsDomain() {
|
||||
ips, _, err := h.dns.LookupIP(addr.Domain(), dns.IPOption{
|
||||
IPv4Enable: h.hasIPv4 && h.conf.preferIP4(),
|
||||
IPv6Enable: h.hasIPv6 && h.conf.preferIP6(),
|
||||
})
|
||||
{ // Resolve fallback
|
||||
if (len(ips) == 0 || err != nil) && h.conf.hasFallback() {
|
||||
ips, _, err = h.dns.LookupIP(addr.Domain(), dns.IPOption{
|
||||
IPv4Enable: h.hasIPv4 && h.conf.fallbackIP4(),
|
||||
IPv6Enable: h.hasIPv6 && h.conf.fallbackIP6(),
|
||||
})
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return errors.New("failed to lookup DNS").Base(err)
|
||||
} else if len(ips) == 0 {
|
||||
return dns.ErrEmptyResponse
|
||||
}
|
||||
addr = net.IPAddress(ips[dice.Roll(len(ips))])
|
||||
}
|
||||
destination.Address = addr
|
||||
|
||||
var newCtx context.Context
|
||||
var newCancel context.CancelFunc
|
||||
@@ -171,64 +193,59 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
||||
newCtx, newCancel = context.WithCancel(context.Background())
|
||||
}
|
||||
|
||||
sessionPolicy := h.policyManager.ForLevel(0)
|
||||
p := h.policyManager.ForLevel(0)
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
timer := signal.CancelAfterInactivity(ctx, func() {
|
||||
cancel()
|
||||
if newCancel != nil {
|
||||
newCancel()
|
||||
}
|
||||
}, sessionPolicy.Timeouts.ConnectionIdle)
|
||||
}, p.Timeouts.ConnectionIdle)
|
||||
addrPort := netip.AddrPortFrom(toNetIpAddr(addr), destination.Port.Value())
|
||||
|
||||
if newCtx != nil {
|
||||
ctx = newCtx
|
||||
}
|
||||
var requestFunc func() error
|
||||
var responseFunc func() error
|
||||
|
||||
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 = h.tnet.DialContextTCPAddrPort(timeoutCtx, addrPort)
|
||||
timeoutCancel()
|
||||
} else {
|
||||
conn, err = h.tnet.DialContextTCPAddrPort(ctx, addrPort)
|
||||
}
|
||||
if command == protocol.RequestCommandTCP {
|
||||
conn, err := h.net.DialContextTCPAddrPort(ctx, addrPort)
|
||||
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 := h.tnet.DialUDPAddrPort(netip.AddrPort{}, addrPort)
|
||||
|
||||
requestFunc = func() error {
|
||||
defer timer.SetTimeout(p.Timeouts.DownlinkOnly)
|
||||
return buf.Copy(link.Reader, buf.NewWriter(conn), buf.UpdateActivity(timer))
|
||||
}
|
||||
responseFunc = func() error {
|
||||
defer timer.SetTimeout(p.Timeouts.UplinkOnly)
|
||||
return buf.Copy(buf.NewReader(conn), link.Writer, buf.UpdateActivity(timer))
|
||||
}
|
||||
} else if command == protocol.RequestCommandUDP {
|
||||
conn, err := h.net.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,
|
||||
resolveFunc: h.resolveRemote,
|
||||
dest: gonet.UDPAddrFromAddrPort(addrPort),
|
||||
|
||||
conn = &udpConnClient{
|
||||
Conn: conn,
|
||||
dest: destination,
|
||||
}
|
||||
|
||||
requestFunc = func() error {
|
||||
defer timer.SetTimeout(p.Timeouts.DownlinkOnly)
|
||||
return buf.Copy(link.Reader, buf.NewWriter(conn), buf.UpdateActivity(timer))
|
||||
}
|
||||
responseFunc = func() error {
|
||||
defer timer.SetTimeout(p.Timeouts.UplinkOnly)
|
||||
return buf.Copy(buf.NewReader(conn), link.Writer, buf.UpdateActivity(timer))
|
||||
}
|
||||
reader = c
|
||||
writer = c
|
||||
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))
|
||||
if newCtx != nil {
|
||||
ctx = newCtx
|
||||
}
|
||||
|
||||
responseDonePost := task.OnSuccess(responseFunc, task.Close(link.Writer))
|
||||
@@ -241,191 +258,108 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Handler) Close() (err error) {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
if h.dev != nil {
|
||||
h.dev.Close()
|
||||
h.dev = nil
|
||||
h.tun = nil
|
||||
} else if h.tun != nil {
|
||||
h.tun.Close()
|
||||
h.tun = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Handler) init(ctx context.Context) error {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
if h.dev != nil {
|
||||
return nil
|
||||
}
|
||||
resolveFunc := h.resolveLocal
|
||||
listenFunc := func() (net.PacketConn, error) {
|
||||
dest, err := net.ParseDestination("udp:" + h.conf.Peers[0].Endpoint)
|
||||
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
|
||||
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 {
|
||||
pktConn.Close()
|
||||
return nil, errors.New("mask err").Base(err)
|
||||
}
|
||||
pktConn = newConn
|
||||
}
|
||||
if h.uplinkCounter != nil || h.downlinkCounter != nil {
|
||||
pktConn = &PacketCounterConnection{
|
||||
PacketConn: pktConn,
|
||||
ReadCounter: h.downlinkCounter,
|
||||
WriteCounter: h.uplinkCounter,
|
||||
}
|
||||
}
|
||||
return pktConn, nil
|
||||
}
|
||||
bind := &bind{}
|
||||
logger := &device.Logger{
|
||||
Verbosef: func(format string, args ...any) {
|
||||
log.Record(&log.GeneralMessage{
|
||||
Severity: log.Severity_Debug,
|
||||
Content: fmt.Sprintf(format, args...),
|
||||
})
|
||||
},
|
||||
Errorf: func(format string, args ...any) {
|
||||
log.Record(&log.GeneralMessage{
|
||||
Severity: log.Severity_Error,
|
||||
Content: fmt.Sprintf(format, args...),
|
||||
})
|
||||
},
|
||||
}
|
||||
dev := device.NewDevice(h.tun, bind, logger)
|
||||
bind.resolveFunc = resolveFunc
|
||||
bind.listenFunc = listenFunc
|
||||
bind.downFunc = dev.Down
|
||||
bind.reserved = h.conf.Reserved
|
||||
var cfg strings.Builder
|
||||
cfg.WriteString("private_key=" + h.conf.SecretKey + "\n")
|
||||
for _, peer := range h.conf.Peers {
|
||||
cfg.WriteString("public_key=" + peer.PublicKey + "\n")
|
||||
if peer.PreSharedKey != "" {
|
||||
cfg.WriteString("preshared_key=" + peer.PreSharedKey + "\n")
|
||||
}
|
||||
cfg.WriteString("endpoint=" + peer.Endpoint + "\n")
|
||||
for _, ip := range peer.AllowedIps {
|
||||
cfg.WriteString("allowed_ip=" + ip + "\n")
|
||||
}
|
||||
if peer.KeepAlive != "" {
|
||||
cfg.WriteString("persistent_keepalive_interval=" + peer.KeepAlive + "\n")
|
||||
}
|
||||
}
|
||||
err := dev.IpcSet(cfg.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = dev.Up()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h.dev = dev
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Handler) resolveLocal(host string) (net.IP, error) {
|
||||
return resolveDomain(host, h.conf.DomainStrategy, func(host string) ([]net.IP, error) {
|
||||
ips, _, err := h.dns.LookupIP(host, dns.IPOption{IPv4Enable: true, IPv6Enable: true})
|
||||
return ips, err
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Handler) resolveRemote(host string) (net.IP, error) {
|
||||
return resolveDomain(host, h.conf.DomainStrategy, func(host string) ([]net.IP, error) {
|
||||
addrs, err := h.tnet.LookupHost(host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ips := make([]net.IP, 0, len(addrs))
|
||||
for _, addr := range addrs {
|
||||
ips = append(ips, net.ParseIP(addr))
|
||||
}
|
||||
return ips, nil
|
||||
})
|
||||
}
|
||||
|
||||
func resolveDomain(host string, strategy DeviceConfig_DomainStrategy, lookupIP func(host string) ([]net.IP, error)) (net.IP, error) {
|
||||
if ip := net.ParseIP(host); ip != nil {
|
||||
return ip, nil
|
||||
}
|
||||
ips, err := lookupIP(host)
|
||||
// creates a tun interface on netstack given a configuration
|
||||
func (h *Handler) makeVirtualTun() (Tunnel, error) {
|
||||
t, err := h.conf.createTun()(h.endpoints, int(h.conf.Mtu), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(ips) == 0 {
|
||||
return nil, dns.ErrEmptyResponse
|
||||
|
||||
h.bind.dnsOption.IPv4Enable = h.hasIPv4
|
||||
h.bind.dnsOption.IPv6Enable = h.hasIPv6
|
||||
|
||||
if err = t.BuildDevice(h.createIPCRequest(), h.bind); err != nil {
|
||||
_ = t.Close()
|
||||
return nil, err
|
||||
}
|
||||
var got4, got6 []net.IP
|
||||
for _, ip := range ips {
|
||||
if ip.To4() != nil {
|
||||
got4 = append(got4, ip)
|
||||
} else {
|
||||
got6 = append(got6, ip)
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// serialize the config into an IPC request
|
||||
func (h *Handler) createIPCRequest() string {
|
||||
var request strings.Builder
|
||||
|
||||
request.WriteString(fmt.Sprintf("private_key=%s\n", h.conf.SecretKey))
|
||||
|
||||
if !h.conf.IsClient {
|
||||
// placeholder, we'll handle actual port listening on Xray
|
||||
request.WriteString("listen_port=1337\n")
|
||||
}
|
||||
|
||||
for _, peer := range h.conf.Peers {
|
||||
if peer.PublicKey != "" {
|
||||
request.WriteString(fmt.Sprintf("public_key=%s\n", peer.PublicKey))
|
||||
}
|
||||
|
||||
if peer.PreSharedKey != "" {
|
||||
request.WriteString(fmt.Sprintf("preshared_key=%s\n", peer.PreSharedKey))
|
||||
}
|
||||
|
||||
address, port, err := net.SplitHostPort(peer.Endpoint)
|
||||
if err != nil {
|
||||
errors.LogError(h.bind.ctx, "failed to split endpoint ", peer.Endpoint, " into address and port")
|
||||
}
|
||||
addr := net.ParseAddress(address)
|
||||
if addr.Family().IsDomain() {
|
||||
dialerIp := h.bind.dialer.DestIpAddress()
|
||||
if dialerIp != nil {
|
||||
addr = net.ParseAddress(dialerIp.String())
|
||||
errors.LogInfo(h.bind.ctx, "createIPCRequest use dialer dest ip: ", addr)
|
||||
} else {
|
||||
ips, _, err := h.dns.LookupIP(addr.Domain(), dns.IPOption{
|
||||
IPv4Enable: h.conf.preferIP4(),
|
||||
IPv6Enable: h.conf.preferIP6(),
|
||||
})
|
||||
{ // Resolve fallback
|
||||
if (len(ips) == 0 || err != nil) && h.conf.hasFallback() {
|
||||
ips, _, err = h.dns.LookupIP(addr.Domain(), dns.IPOption{
|
||||
IPv4Enable: h.conf.fallbackIP4(),
|
||||
IPv6Enable: h.conf.fallbackIP6(),
|
||||
})
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
errors.LogInfoInner(h.bind.ctx, err, "createIPCRequest failed to lookup DNS")
|
||||
} else if len(ips) == 0 {
|
||||
errors.LogInfo(h.bind.ctx, "createIPCRequest empty lookup DNS")
|
||||
} else {
|
||||
addr = net.IPAddress(ips[dice.Roll(len(ips))])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if peer.Endpoint != "" {
|
||||
request.WriteString(fmt.Sprintf("endpoint=%s:%s\n", addr, port))
|
||||
}
|
||||
|
||||
for _, ip := range peer.AllowedIps {
|
||||
request.WriteString(fmt.Sprintf("allowed_ip=%s\n", ip))
|
||||
}
|
||||
|
||||
if peer.KeepAlive != 0 {
|
||||
request.WriteString(fmt.Sprintf("persistent_keepalive_interval=%d\n", peer.KeepAlive))
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
return got[dice.Roll(len(got))], nil
|
||||
|
||||
return request.String()[:request.Len()]
|
||||
}
|
||||
|
||||
type udpConnClient struct {
|
||||
net.PacketConn
|
||||
resolveFunc func(host string) (net.IP, error)
|
||||
dest *net.UDPAddr
|
||||
net.Conn
|
||||
dest net.Destination
|
||||
}
|
||||
|
||||
func (c *udpConnClient) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
||||
b := buf.New()
|
||||
b.Resize(0, buf.Size)
|
||||
n, addr, err := c.PacketConn.ReadFrom(b.Bytes())
|
||||
n, addr, err := c.Conn.(net.PacketConn).ReadFrom(b.Bytes())
|
||||
if err != nil {
|
||||
b.Release()
|
||||
return nil, err
|
||||
}
|
||||
if addr == nil { // should never hit
|
||||
addr = c.dest.RawNetAddr()
|
||||
}
|
||||
b.Resize(0, int32(n))
|
||||
|
||||
b.UDP = &net.Destination{
|
||||
@@ -441,22 +375,9 @@ func (c *udpConnClient) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
||||
for i, b := range mb {
|
||||
dst := c.dest
|
||||
if b.UDP != nil {
|
||||
if b.UDP.Address.Family().IsDomain() {
|
||||
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)
|
||||
}
|
||||
dst = *b.UDP
|
||||
}
|
||||
_, err := c.PacketConn.WriteTo(b.Bytes(), dst)
|
||||
_, err := c.Conn.(net.PacketConn).WriteTo(b.Bytes(), dst.RawNetAddr())
|
||||
if err != nil {
|
||||
buf.ReleaseMulti(mb[i:])
|
||||
return err
|
||||
@@ -465,25 +386,3 @@ func (c *udpConnClient) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type PacketCounterConnection struct {
|
||||
net.PacketConn
|
||||
ReadCounter stats.Counter
|
||||
WriteCounter stats.Counter
|
||||
}
|
||||
|
||||
func (c *PacketCounterConnection) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||
n, addr, err = c.PacketConn.ReadFrom(p)
|
||||
if err == nil && c.ReadCounter != nil {
|
||||
c.ReadCounter.Add(int64(n))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *PacketCounterConnection) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
n, err = c.PacketConn.WriteTo(p, addr)
|
||||
if err == nil && c.WriteCounter != nil {
|
||||
c.WriteCounter.Add(int64(n))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1 +1,54 @@
|
||||
package wireguard
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
)
|
||||
|
||||
func (c *DeviceConfig) preferIP4() bool {
|
||||
return c.DomainStrategy == DeviceConfig_FORCE_IP ||
|
||||
c.DomainStrategy == DeviceConfig_FORCE_IP4 ||
|
||||
c.DomainStrategy == DeviceConfig_FORCE_IP46
|
||||
}
|
||||
|
||||
func (c *DeviceConfig) preferIP6() bool {
|
||||
return c.DomainStrategy == DeviceConfig_FORCE_IP ||
|
||||
c.DomainStrategy == DeviceConfig_FORCE_IP6 ||
|
||||
c.DomainStrategy == DeviceConfig_FORCE_IP64
|
||||
}
|
||||
|
||||
func (c *DeviceConfig) hasFallback() bool {
|
||||
return c.DomainStrategy == DeviceConfig_FORCE_IP46 || c.DomainStrategy == DeviceConfig_FORCE_IP64
|
||||
}
|
||||
|
||||
func (c *DeviceConfig) fallbackIP4() bool {
|
||||
return c.DomainStrategy == DeviceConfig_FORCE_IP64
|
||||
}
|
||||
|
||||
func (c *DeviceConfig) fallbackIP6() bool {
|
||||
return c.DomainStrategy == DeviceConfig_FORCE_IP46
|
||||
}
|
||||
|
||||
func (c *DeviceConfig) createTun() tunCreator {
|
||||
if !c.IsClient {
|
||||
// See tun_linux.go createKernelTun()
|
||||
errors.LogWarning(context.Background(), "Using gVisor TUN. WG inbound doesn't support kernel TUN yet.")
|
||||
return createGVisorTun
|
||||
}
|
||||
if c.NoKernelTun {
|
||||
errors.LogWarning(context.Background(), "Using gVisor TUN. NoKernelTun is set to true.")
|
||||
return createGVisorTun
|
||||
}
|
||||
kernelTunSupported, err := KernelTunSupported()
|
||||
if err != nil {
|
||||
errors.LogWarning(context.Background(), "Using gVisor TUN. Failed to check kernel TUN support:", err)
|
||||
return createGVisorTun
|
||||
}
|
||||
if !kernelTunSupported {
|
||||
errors.LogWarning(context.Background(), "Using gVisor TUN. Kernel TUN is not supported on your OS, or your permission is insufficient.")
|
||||
return createGVisorTun
|
||||
}
|
||||
errors.LogWarning(context.Background(), "Using kernel TUN.")
|
||||
return createKernelTun
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ type PeerConfig struct {
|
||||
PublicKey string `protobuf:"bytes,1,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"`
|
||||
PreSharedKey string `protobuf:"bytes,2,opt,name=pre_shared_key,json=preSharedKey,proto3" json:"pre_shared_key,omitempty"`
|
||||
Endpoint string `protobuf:"bytes,3,opt,name=endpoint,proto3" json:"endpoint,omitempty"`
|
||||
KeepAlive string `protobuf:"bytes,4,opt,name=keep_alive,json=keepAlive,proto3" json:"keep_alive,omitempty"`
|
||||
KeepAlive uint32 `protobuf:"varint,4,opt,name=keep_alive,json=keepAlive,proto3" json:"keep_alive,omitempty"`
|
||||
AllowedIps []string `protobuf:"bytes,5,rep,name=allowed_ips,json=allowedIps,proto3" json:"allowed_ips,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
@@ -138,11 +138,11 @@ func (x *PeerConfig) GetEndpoint() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *PeerConfig) GetKeepAlive() string {
|
||||
func (x *PeerConfig) GetKeepAlive() uint32 {
|
||||
if x != nil {
|
||||
return x.KeepAlive
|
||||
}
|
||||
return ""
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *PeerConfig) GetAllowedIps() []string {
|
||||
@@ -158,6 +158,7 @@ type DeviceConfig struct {
|
||||
Endpoint []string `protobuf:"bytes,2,rep,name=endpoint,proto3" json:"endpoint,omitempty"`
|
||||
Peers []*PeerConfig `protobuf:"bytes,3,rep,name=peers,proto3" json:"peers,omitempty"`
|
||||
Mtu int32 `protobuf:"varint,4,opt,name=mtu,proto3" json:"mtu,omitempty"`
|
||||
NumWorkers int32 `protobuf:"varint,5,opt,name=num_workers,json=numWorkers,proto3" json:"num_workers,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"`
|
||||
@@ -224,6 +225,13 @@ func (x *DeviceConfig) GetMtu() int32 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *DeviceConfig) GetNumWorkers() int32 {
|
||||
if x != nil {
|
||||
return x.NumWorkers
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *DeviceConfig) GetReserved() []byte {
|
||||
if x != nil {
|
||||
return x.Reserved
|
||||
@@ -264,15 +272,17 @@ const file_proxy_wireguard_config_proto_rawDesc = "" +
|
||||
"\x0epre_shared_key\x18\x02 \x01(\tR\fpreSharedKey\x12\x1a\n" +
|
||||
"\bendpoint\x18\x03 \x01(\tR\bendpoint\x12\x1d\n" +
|
||||
"\n" +
|
||||
"keep_alive\x18\x04 \x01(\tR\tkeepAlive\x12\x1f\n" +
|
||||
"keep_alive\x18\x04 \x01(\rR\tkeepAlive\x12\x1f\n" +
|
||||
"\vallowed_ips\x18\x05 \x03(\tR\n" +
|
||||
"allowedIps\"\xaa\x03\n" +
|
||||
"allowedIps\"\xcb\x03\n" +
|
||||
"\fDeviceConfig\x12\x1d\n" +
|
||||
"\n" +
|
||||
"secret_key\x18\x01 \x01(\tR\tsecretKey\x12\x1a\n" +
|
||||
"\bendpoint\x18\x02 \x03(\tR\bendpoint\x126\n" +
|
||||
"\x05peers\x18\x03 \x03(\v2 .xray.proxy.wireguard.PeerConfigR\x05peers\x12\x10\n" +
|
||||
"\x03mtu\x18\x04 \x01(\x05R\x03mtu\x12\x1a\n" +
|
||||
"\x03mtu\x18\x04 \x01(\x05R\x03mtu\x12\x1f\n" +
|
||||
"\vnum_workers\x18\x05 \x01(\x05R\n" +
|
||||
"numWorkers\x12\x1a\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" +
|
||||
|
||||
@@ -10,7 +10,7 @@ message PeerConfig {
|
||||
string public_key = 1;
|
||||
string pre_shared_key = 2;
|
||||
string endpoint = 3;
|
||||
string keep_alive = 4;
|
||||
uint32 keep_alive = 4;
|
||||
repeated string allowed_ips = 5;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ message DeviceConfig {
|
||||
repeated string endpoint = 2;
|
||||
repeated PeerConfig peers = 3;
|
||||
int32 mtu = 4;
|
||||
|
||||
int32 num_workers = 5;
|
||||
bytes reserved = 6;
|
||||
DomainStrategy domain_strategy = 7;
|
||||
bool is_client = 8;
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2022 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package gvisortun
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"os"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
"gvisor.dev/gvisor/pkg/buffer"
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
|
||||
)
|
||||
|
||||
type netTun struct {
|
||||
ep *channel.Endpoint
|
||||
stack *stack.Stack
|
||||
events chan tun.Event
|
||||
notifyHandle *channel.NotificationHandle
|
||||
incomingPacket chan *buffer.View
|
||||
mtu int
|
||||
hasV4, hasV6 bool
|
||||
closeOnce sync.Once
|
||||
}
|
||||
|
||||
type Net netTun
|
||||
|
||||
func CreateNetTUN(localAddresses []netip.Addr, mtu int, promiscuousMode bool) (tun.Device, *Net, *stack.Stack, error) {
|
||||
opts := stack.Options{
|
||||
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
|
||||
TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4},
|
||||
HandleLocal: !promiscuousMode,
|
||||
}
|
||||
dev := &netTun{
|
||||
ep: channel.New(1024, uint32(mtu), ""),
|
||||
stack: stack.New(opts),
|
||||
events: make(chan tun.Event, 10),
|
||||
incomingPacket: make(chan *buffer.View),
|
||||
mtu: mtu,
|
||||
}
|
||||
sackEnabledOpt := tcpip.TCPSACKEnabled(true) // TCP SACK is disabled by default
|
||||
tcpipErr := dev.stack.SetTransportProtocolOption(tcp.ProtocolNumber, &sackEnabledOpt)
|
||||
if tcpipErr != nil {
|
||||
return nil, nil, dev.stack, fmt.Errorf("could not enable TCP SACK: %v", tcpipErr)
|
||||
}
|
||||
dev.notifyHandle = dev.ep.AddNotify(dev)
|
||||
tcpipErr = dev.stack.CreateNIC(1, dev.ep)
|
||||
if tcpipErr != nil {
|
||||
return nil, nil, dev.stack, fmt.Errorf("CreateNIC: %v", tcpipErr)
|
||||
}
|
||||
for _, ip := range localAddresses {
|
||||
var protoNumber tcpip.NetworkProtocolNumber
|
||||
if ip.Is4() {
|
||||
protoNumber = ipv4.ProtocolNumber
|
||||
} else if ip.Is6() {
|
||||
protoNumber = ipv6.ProtocolNumber
|
||||
}
|
||||
protoAddr := tcpip.ProtocolAddress{
|
||||
Protocol: protoNumber,
|
||||
AddressWithPrefix: tcpip.AddrFromSlice(ip.AsSlice()).WithPrefix(),
|
||||
}
|
||||
tcpipErr := dev.stack.AddProtocolAddress(1, protoAddr, stack.AddressProperties{})
|
||||
if tcpipErr != nil {
|
||||
return nil, nil, dev.stack, fmt.Errorf("AddProtocolAddress(%v): %v", ip, tcpipErr)
|
||||
}
|
||||
if ip.Is4() {
|
||||
dev.hasV4 = true
|
||||
} else if ip.Is6() {
|
||||
dev.hasV6 = true
|
||||
}
|
||||
}
|
||||
if dev.hasV4 {
|
||||
dev.stack.AddRoute(tcpip.Route{Destination: header.IPv4EmptySubnet, NIC: 1})
|
||||
}
|
||||
if dev.hasV6 {
|
||||
dev.stack.AddRoute(tcpip.Route{Destination: header.IPv6EmptySubnet, NIC: 1})
|
||||
}
|
||||
if promiscuousMode {
|
||||
// enable promiscuous mode to handle all packets processed by netstack
|
||||
dev.stack.SetPromiscuousMode(1, true)
|
||||
dev.stack.SetSpoofing(1, true)
|
||||
}
|
||||
|
||||
dev.events <- tun.EventUp
|
||||
return dev, (*Net)(dev), dev.stack, nil
|
||||
}
|
||||
|
||||
// Name implements tun.Device
|
||||
func (tun *netTun) Name() (string, error) {
|
||||
return "go", nil
|
||||
}
|
||||
|
||||
// File implements tun.Device
|
||||
func (tun *netTun) File() *os.File {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Events implements tun.Device
|
||||
func (tun *netTun) Events() <-chan tun.Event {
|
||||
return tun.events
|
||||
}
|
||||
|
||||
// Read implements tun.Device
|
||||
func (tun *netTun) Read(buf [][]byte, sizes []int, offset int) (int, error) {
|
||||
view, ok := <-tun.incomingPacket
|
||||
if !ok {
|
||||
return 0, os.ErrClosed
|
||||
}
|
||||
|
||||
n, err := view.Read(buf[0][offset:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
sizes[0] = n
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
// Write implements tun.Device
|
||||
func (tun *netTun) Write(buf [][]byte, offset int) (int, error) {
|
||||
for _, buf := range buf {
|
||||
packet := buf[offset:]
|
||||
if len(packet) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
pkb := stack.NewPacketBuffer(stack.PacketBufferOptions{Payload: buffer.MakeWithData(packet)})
|
||||
switch packet[0] >> 4 {
|
||||
case 4:
|
||||
tun.ep.InjectInbound(header.IPv4ProtocolNumber, pkb)
|
||||
case 6:
|
||||
tun.ep.InjectInbound(header.IPv6ProtocolNumber, pkb)
|
||||
default:
|
||||
return 0, syscall.EAFNOSUPPORT
|
||||
}
|
||||
}
|
||||
return len(buf), nil
|
||||
}
|
||||
|
||||
// WriteNotify implements channel.Notification
|
||||
func (tun *netTun) WriteNotify() {
|
||||
pkt := tun.ep.Read()
|
||||
if pkt == nil {
|
||||
return
|
||||
}
|
||||
|
||||
view := pkt.ToView()
|
||||
pkt.DecRef()
|
||||
|
||||
tun.incomingPacket <- view
|
||||
}
|
||||
|
||||
// Close implements tun.Device
|
||||
func (tun *netTun) Close() error {
|
||||
tun.closeOnce.Do(func() {
|
||||
tun.stack.RemoveNIC(1)
|
||||
tun.stack.Close()
|
||||
tun.ep.RemoveNotify(tun.notifyHandle)
|
||||
tun.ep.Close()
|
||||
|
||||
close(tun.events)
|
||||
|
||||
close(tun.incomingPacket)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// MTU implements tun.Device
|
||||
func (tun *netTun) MTU() (int, error) {
|
||||
return tun.mtu, nil
|
||||
}
|
||||
|
||||
// BatchSize implements tun.Device
|
||||
func (tun *netTun) BatchSize() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
func convertToFullAddr(endpoint netip.AddrPort) (tcpip.FullAddress, tcpip.NetworkProtocolNumber) {
|
||||
var protoNumber tcpip.NetworkProtocolNumber
|
||||
if endpoint.Addr().Is4() {
|
||||
protoNumber = ipv4.ProtocolNumber
|
||||
} else {
|
||||
protoNumber = ipv6.ProtocolNumber
|
||||
}
|
||||
return tcpip.FullAddress{
|
||||
NIC: 1,
|
||||
Addr: tcpip.AddrFromSlice(endpoint.Addr().AsSlice()),
|
||||
Port: endpoint.Port(),
|
||||
}, protoNumber
|
||||
}
|
||||
|
||||
func (net *Net) DialContextTCPAddrPort(ctx context.Context, addr netip.AddrPort) (*gonet.TCPConn, error) {
|
||||
fa, pn := convertToFullAddr(addr)
|
||||
return gonet.DialContextTCP(ctx, net.stack, fa, pn)
|
||||
}
|
||||
|
||||
func (net *Net) DialUDPAddrPort(laddr, raddr netip.AddrPort) (*gonet.UDPConn, error) {
|
||||
var lfa, rfa *tcpip.FullAddress
|
||||
var pn tcpip.NetworkProtocolNumber
|
||||
if laddr.IsValid() || laddr.Port() > 0 {
|
||||
var addr tcpip.FullAddress
|
||||
addr, pn = convertToFullAddr(laddr)
|
||||
lfa = &addr
|
||||
}
|
||||
if raddr.IsValid() || raddr.Port() > 0 {
|
||||
var addr tcpip.FullAddress
|
||||
addr, pn = convertToFullAddr(raddr)
|
||||
rfa = &addr
|
||||
rfa = nil // do not ep connect
|
||||
}
|
||||
return gonet.DialUDP(net.stack, lfa, rfa, pn)
|
||||
}
|
||||
@@ -1,690 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package wireguard
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/transport/internet"
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
|
||||
"golang.org/x/net/dns/dnsmessage"
|
||||
"gvisor.dev/gvisor/pkg/buffer"
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
|
||||
)
|
||||
|
||||
type netTun struct {
|
||||
ep *channel.Endpoint
|
||||
stack *stack.Stack
|
||||
events chan tun.Event
|
||||
notifyHandle *channel.NotificationHandle
|
||||
incomingPacket chan *buffer.View
|
||||
mtu int
|
||||
dnsServers []netip.Addr
|
||||
hasV4, hasV6 bool
|
||||
}
|
||||
|
||||
func CreateNetTUN(localAddresses, dnsServers []netip.Addr, mtu int, handleLocal bool) (tun.Device, *Net, *stack.Stack, error) {
|
||||
opts := stack.Options{
|
||||
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
|
||||
TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4},
|
||||
HandleLocal: handleLocal,
|
||||
}
|
||||
dev := &netTun{
|
||||
ep: channel.New(1024, uint32(mtu), ""),
|
||||
stack: stack.New(opts),
|
||||
events: make(chan tun.Event, 10),
|
||||
incomingPacket: make(chan *buffer.View),
|
||||
dnsServers: dnsServers,
|
||||
mtu: mtu,
|
||||
}
|
||||
sackEnabledOpt := tcpip.TCPSACKEnabled(true) // TCP SACK is disabled by default
|
||||
tcpipErr := dev.stack.SetTransportProtocolOption(tcp.ProtocolNumber, &sackEnabledOpt)
|
||||
if tcpipErr != nil {
|
||||
return nil, nil, nil, fmt.Errorf("could not enable TCP SACK: %v", tcpipErr)
|
||||
}
|
||||
dev.notifyHandle = dev.ep.AddNotify(dev)
|
||||
tcpipErr = dev.stack.CreateNIC(1, dev.ep)
|
||||
if tcpipErr != nil {
|
||||
return nil, nil, nil, fmt.Errorf("CreateNIC: %v", tcpipErr)
|
||||
}
|
||||
for _, ip := range localAddresses {
|
||||
var protoNumber tcpip.NetworkProtocolNumber
|
||||
if ip.Is4() {
|
||||
protoNumber = ipv4.ProtocolNumber
|
||||
} else if ip.Is6() {
|
||||
protoNumber = ipv6.ProtocolNumber
|
||||
}
|
||||
protoAddr := tcpip.ProtocolAddress{
|
||||
Protocol: protoNumber,
|
||||
AddressWithPrefix: tcpip.AddrFromSlice(ip.AsSlice()).WithPrefix(),
|
||||
}
|
||||
tcpipErr := dev.stack.AddProtocolAddress(1, protoAddr, stack.AddressProperties{})
|
||||
if tcpipErr != nil {
|
||||
return nil, nil, nil, fmt.Errorf("AddProtocolAddress(%v): %v", ip, tcpipErr)
|
||||
}
|
||||
if ip.Is4() {
|
||||
dev.hasV4 = true
|
||||
} else if ip.Is6() {
|
||||
dev.hasV6 = true
|
||||
}
|
||||
}
|
||||
if dev.hasV4 {
|
||||
dev.stack.AddRoute(tcpip.Route{Destination: header.IPv4EmptySubnet, NIC: 1})
|
||||
}
|
||||
if dev.hasV6 {
|
||||
dev.stack.AddRoute(tcpip.Route{Destination: header.IPv6EmptySubnet, NIC: 1})
|
||||
}
|
||||
|
||||
tnet := &Net{
|
||||
DialContextTCPAddrPort: dev.DialContextTCPAddrPort,
|
||||
DialUDPAddrPort: dev.DialUDPAddrPort,
|
||||
dnsServers: dev.dnsServers,
|
||||
hasV4: dev.hasV4,
|
||||
hasV6: dev.hasV6,
|
||||
}
|
||||
|
||||
dev.events <- tun.EventUp
|
||||
return dev, tnet, dev.stack, nil
|
||||
}
|
||||
|
||||
func (tun *netTun) Name() (string, error) {
|
||||
return "go", nil
|
||||
}
|
||||
|
||||
func (tun *netTun) File() *os.File {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tun *netTun) Events() <-chan tun.Event {
|
||||
return tun.events
|
||||
}
|
||||
|
||||
func (tun *netTun) Read(buf [][]byte, sizes []int, offset int) (int, error) {
|
||||
view, ok := <-tun.incomingPacket
|
||||
if !ok {
|
||||
return 0, os.ErrClosed
|
||||
}
|
||||
|
||||
n, err := view.Read(buf[0][offset:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
sizes[0] = n
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
func (tun *netTun) Write(buf [][]byte, offset int) (int, error) {
|
||||
for _, buf := range buf {
|
||||
packet := buf[offset:]
|
||||
if len(packet) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
pkb := stack.NewPacketBuffer(stack.PacketBufferOptions{Payload: buffer.MakeWithData(packet)})
|
||||
switch packet[0] >> 4 {
|
||||
case 4:
|
||||
tun.ep.InjectInbound(header.IPv4ProtocolNumber, pkb)
|
||||
case 6:
|
||||
tun.ep.InjectInbound(header.IPv6ProtocolNumber, pkb)
|
||||
default:
|
||||
return 0, syscall.EAFNOSUPPORT
|
||||
}
|
||||
}
|
||||
return len(buf), nil
|
||||
}
|
||||
|
||||
func (tun *netTun) WriteNotify() {
|
||||
pkt := tun.ep.Read()
|
||||
if pkt == nil {
|
||||
return
|
||||
}
|
||||
|
||||
view := pkt.ToView()
|
||||
pkt.DecRef()
|
||||
|
||||
tun.incomingPacket <- view
|
||||
}
|
||||
|
||||
func (tun *netTun) Close() error {
|
||||
tun.stack.RemoveNIC(1)
|
||||
tun.stack.Close()
|
||||
tun.ep.RemoveNotify(tun.notifyHandle)
|
||||
tun.ep.Close()
|
||||
|
||||
if tun.events != nil {
|
||||
close(tun.events)
|
||||
}
|
||||
|
||||
if tun.incomingPacket != nil {
|
||||
close(tun.incomingPacket)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tun *netTun) MTU() (int, error) {
|
||||
return tun.mtu, nil
|
||||
}
|
||||
|
||||
func (tun *netTun) BatchSize() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
func (tun *netTun) DialContextTCPAddrPort(ctx context.Context, addr netip.AddrPort) (net.Conn, error) {
|
||||
fa, pn := convertToFullAddr(addr)
|
||||
return gonet.DialContextTCP(ctx, tun.stack, fa, pn)
|
||||
}
|
||||
|
||||
func (tun *netTun) DialUDPAddrPort(laddr, raddr netip.AddrPort) (net.Conn, error) {
|
||||
var pn tcpip.NetworkProtocolNumber = ipv6.ProtocolNumber
|
||||
if raddr.IsValid() || raddr.Port() > 0 {
|
||||
_, pn = convertToFullAddr(raddr)
|
||||
}
|
||||
conn, err := gonet.DialUDP(tun.stack, nil, nil, pn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &internet.PacketConnWrapper{
|
||||
PacketConn: conn,
|
||||
Dest: net.UDPAddrFromAddrPort(raddr),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type Net struct {
|
||||
DialContextTCPAddrPort func(ctx context.Context, addr netip.AddrPort) (net.Conn, error)
|
||||
DialUDPAddrPort func(laddr, raddr netip.AddrPort) (net.Conn, error)
|
||||
dnsServers []netip.Addr
|
||||
hasV4, hasV6 bool
|
||||
}
|
||||
|
||||
func convertToFullAddr(endpoint netip.AddrPort) (tcpip.FullAddress, tcpip.NetworkProtocolNumber) {
|
||||
var protoNumber tcpip.NetworkProtocolNumber
|
||||
if endpoint.Addr().Is4() {
|
||||
protoNumber = ipv4.ProtocolNumber
|
||||
} else {
|
||||
protoNumber = ipv6.ProtocolNumber
|
||||
}
|
||||
return tcpip.FullAddress{
|
||||
NIC: 1,
|
||||
Addr: tcpip.AddrFromSlice(endpoint.Addr().AsSlice()),
|
||||
Port: endpoint.Port(),
|
||||
}, protoNumber
|
||||
}
|
||||
|
||||
var (
|
||||
errNoSuchHost = errors.New("no such host")
|
||||
errLameReferral = errors.New("lame referral")
|
||||
errCannotUnmarshalDNSMessage = errors.New("cannot unmarshal DNS message")
|
||||
errCannotMarshalDNSMessage = errors.New("cannot marshal DNS message")
|
||||
errServerMisbehaving = errors.New("server misbehaving")
|
||||
errInvalidDNSResponse = errors.New("invalid DNS response")
|
||||
errNoAnswerFromDNSServer = errors.New("no answer from DNS server")
|
||||
errServerTemporarilyMisbehaving = errors.New("server misbehaving")
|
||||
errCanceled = errors.New("operation was canceled")
|
||||
errTimeout = errors.New("i/o timeout")
|
||||
)
|
||||
|
||||
func (net *Net) LookupHost(host string) (addrs []string, err error) {
|
||||
return net.LookupContextHost(context.Background(), host)
|
||||
}
|
||||
|
||||
func isDomainName(s string) bool {
|
||||
l := len(s)
|
||||
if l == 0 || l > 254 || l == 254 && s[l-1] != '.' {
|
||||
return false
|
||||
}
|
||||
last := byte('.')
|
||||
nonNumeric := false
|
||||
partlen := 0
|
||||
for i := 0; i < len(s); i++ {
|
||||
c := s[i]
|
||||
switch {
|
||||
default:
|
||||
return false
|
||||
case 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '_':
|
||||
nonNumeric = true
|
||||
partlen++
|
||||
case '0' <= c && c <= '9':
|
||||
partlen++
|
||||
case c == '-':
|
||||
if last == '.' {
|
||||
return false
|
||||
}
|
||||
partlen++
|
||||
nonNumeric = true
|
||||
case c == '.':
|
||||
if last == '.' || last == '-' {
|
||||
return false
|
||||
}
|
||||
if partlen > 63 || partlen == 0 {
|
||||
return false
|
||||
}
|
||||
partlen = 0
|
||||
}
|
||||
last = c
|
||||
}
|
||||
if last == '-' || partlen > 63 {
|
||||
return false
|
||||
}
|
||||
return nonNumeric
|
||||
}
|
||||
|
||||
func randU16() uint16 {
|
||||
var b [2]byte
|
||||
_, err := rand.Read(b[:])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return binary.LittleEndian.Uint16(b[:])
|
||||
}
|
||||
|
||||
func newRequest(q dnsmessage.Question) (id uint16, udpReq, tcpReq []byte, err error) {
|
||||
id = randU16()
|
||||
b := dnsmessage.NewBuilder(make([]byte, 2, 514), dnsmessage.Header{ID: id, RecursionDesired: true})
|
||||
b.EnableCompression()
|
||||
if err := b.StartQuestions(); err != nil {
|
||||
return 0, nil, nil, err
|
||||
}
|
||||
if err := b.Question(q); err != nil {
|
||||
return 0, nil, nil, err
|
||||
}
|
||||
tcpReq, err = b.Finish()
|
||||
udpReq = tcpReq[2:]
|
||||
l := len(tcpReq) - 2
|
||||
tcpReq[0] = byte(l >> 8)
|
||||
tcpReq[1] = byte(l)
|
||||
return id, udpReq, tcpReq, err
|
||||
}
|
||||
|
||||
func equalASCIIName(x, y dnsmessage.Name) bool {
|
||||
if x.Length != y.Length {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < int(x.Length); i++ {
|
||||
a := x.Data[i]
|
||||
b := y.Data[i]
|
||||
if 'A' <= a && a <= 'Z' {
|
||||
a += 0x20
|
||||
}
|
||||
if 'A' <= b && b <= 'Z' {
|
||||
b += 0x20
|
||||
}
|
||||
if a != b {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func checkResponse(reqID uint16, reqQues dnsmessage.Question, respHdr dnsmessage.Header, respQues dnsmessage.Question) bool {
|
||||
if !respHdr.Response {
|
||||
return false
|
||||
}
|
||||
if reqID != respHdr.ID {
|
||||
return false
|
||||
}
|
||||
if reqQues.Type != respQues.Type || reqQues.Class != respQues.Class || !equalASCIIName(reqQues.Name, respQues.Name) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func dnsPacketRoundTrip(c net.Conn, id uint16, query dnsmessage.Question, b []byte) (dnsmessage.Parser, dnsmessage.Header, error) {
|
||||
if _, err := c.Write(b); err != nil {
|
||||
return dnsmessage.Parser{}, dnsmessage.Header{}, err
|
||||
}
|
||||
b = make([]byte, 512)
|
||||
for {
|
||||
n, err := c.Read(b)
|
||||
if err != nil {
|
||||
return dnsmessage.Parser{}, dnsmessage.Header{}, err
|
||||
}
|
||||
var p dnsmessage.Parser
|
||||
h, err := p.Start(b[:n])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
q, err := p.Question()
|
||||
if err != nil || !checkResponse(id, query, h, q) {
|
||||
continue
|
||||
}
|
||||
return p, h, nil
|
||||
}
|
||||
}
|
||||
|
||||
func dnsStreamRoundTrip(c net.Conn, id uint16, query dnsmessage.Question, b []byte) (dnsmessage.Parser, dnsmessage.Header, error) {
|
||||
if _, err := c.Write(b); err != nil {
|
||||
return dnsmessage.Parser{}, dnsmessage.Header{}, err
|
||||
}
|
||||
b = make([]byte, 1280)
|
||||
if _, err := io.ReadFull(c, b[:2]); err != nil {
|
||||
return dnsmessage.Parser{}, dnsmessage.Header{}, err
|
||||
}
|
||||
l := int(b[0])<<8 | int(b[1])
|
||||
if l > len(b) {
|
||||
b = make([]byte, l)
|
||||
}
|
||||
n, err := io.ReadFull(c, b[:l])
|
||||
if err != nil {
|
||||
return dnsmessage.Parser{}, dnsmessage.Header{}, err
|
||||
}
|
||||
var p dnsmessage.Parser
|
||||
h, err := p.Start(b[:n])
|
||||
if err != nil {
|
||||
return dnsmessage.Parser{}, dnsmessage.Header{}, errCannotUnmarshalDNSMessage
|
||||
}
|
||||
q, err := p.Question()
|
||||
if err != nil {
|
||||
return dnsmessage.Parser{}, dnsmessage.Header{}, errCannotUnmarshalDNSMessage
|
||||
}
|
||||
if !checkResponse(id, query, h, q) {
|
||||
return dnsmessage.Parser{}, dnsmessage.Header{}, errInvalidDNSResponse
|
||||
}
|
||||
return p, h, nil
|
||||
}
|
||||
|
||||
func (tnet *Net) exchange(ctx context.Context, server netip.Addr, q dnsmessage.Question, timeout time.Duration) (dnsmessage.Parser, dnsmessage.Header, error) {
|
||||
q.Class = dnsmessage.ClassINET
|
||||
id, udpReq, tcpReq, err := newRequest(q)
|
||||
if err != nil {
|
||||
return dnsmessage.Parser{}, dnsmessage.Header{}, errCannotMarshalDNSMessage
|
||||
}
|
||||
|
||||
for _, useUDP := range []bool{true, false} {
|
||||
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(timeout))
|
||||
defer cancel()
|
||||
|
||||
var c net.Conn
|
||||
var err error
|
||||
if useUDP {
|
||||
c, err = tnet.DialUDPAddrPort(netip.AddrPort{}, netip.AddrPortFrom(server, 53))
|
||||
} else {
|
||||
c, err = tnet.DialContextTCPAddrPort(ctx, netip.AddrPortFrom(server, 53))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return dnsmessage.Parser{}, dnsmessage.Header{}, err
|
||||
}
|
||||
if d, ok := ctx.Deadline(); ok && !d.IsZero() {
|
||||
err := c.SetDeadline(d)
|
||||
if err != nil {
|
||||
return dnsmessage.Parser{}, dnsmessage.Header{}, err
|
||||
}
|
||||
}
|
||||
var p dnsmessage.Parser
|
||||
var h dnsmessage.Header
|
||||
if useUDP {
|
||||
p, h, err = dnsPacketRoundTrip(c, id, q, udpReq)
|
||||
} else {
|
||||
p, h, err = dnsStreamRoundTrip(c, id, q, tcpReq)
|
||||
}
|
||||
c.Close()
|
||||
if err != nil {
|
||||
if err == context.Canceled {
|
||||
err = errCanceled
|
||||
} else if err == context.DeadlineExceeded {
|
||||
err = errTimeout
|
||||
}
|
||||
return dnsmessage.Parser{}, dnsmessage.Header{}, err
|
||||
}
|
||||
if err := p.SkipQuestion(); err != dnsmessage.ErrSectionDone {
|
||||
return dnsmessage.Parser{}, dnsmessage.Header{}, errInvalidDNSResponse
|
||||
}
|
||||
if h.Truncated {
|
||||
continue
|
||||
}
|
||||
return p, h, nil
|
||||
}
|
||||
return dnsmessage.Parser{}, dnsmessage.Header{}, errNoAnswerFromDNSServer
|
||||
}
|
||||
|
||||
func checkHeader(p *dnsmessage.Parser, h dnsmessage.Header) error {
|
||||
if h.RCode == dnsmessage.RCodeNameError {
|
||||
return errNoSuchHost
|
||||
}
|
||||
_, err := p.AnswerHeader()
|
||||
if err != nil && err != dnsmessage.ErrSectionDone {
|
||||
return errCannotUnmarshalDNSMessage
|
||||
}
|
||||
if h.RCode == dnsmessage.RCodeSuccess && !h.Authoritative && !h.RecursionAvailable && err == dnsmessage.ErrSectionDone {
|
||||
return errLameReferral
|
||||
}
|
||||
if h.RCode != dnsmessage.RCodeSuccess && h.RCode != dnsmessage.RCodeNameError {
|
||||
if h.RCode == dnsmessage.RCodeServerFailure {
|
||||
return errServerTemporarilyMisbehaving
|
||||
}
|
||||
return errServerMisbehaving
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func skipToAnswer(p *dnsmessage.Parser, qtype dnsmessage.Type) error {
|
||||
for {
|
||||
h, err := p.AnswerHeader()
|
||||
if err == dnsmessage.ErrSectionDone {
|
||||
return errNoSuchHost
|
||||
}
|
||||
if err != nil {
|
||||
return errCannotUnmarshalDNSMessage
|
||||
}
|
||||
if h.Type == qtype {
|
||||
return nil
|
||||
}
|
||||
if err := p.SkipAnswer(); err != nil {
|
||||
return errCannotUnmarshalDNSMessage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (tnet *Net) tryOneName(ctx context.Context, name string, qtype dnsmessage.Type) (dnsmessage.Parser, string, error) {
|
||||
var lastErr error
|
||||
|
||||
n, err := dnsmessage.NewName(name)
|
||||
if err != nil {
|
||||
return dnsmessage.Parser{}, "", errCannotMarshalDNSMessage
|
||||
}
|
||||
q := dnsmessage.Question{
|
||||
Name: n,
|
||||
Type: qtype,
|
||||
Class: dnsmessage.ClassINET,
|
||||
}
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
for _, server := range tnet.dnsServers {
|
||||
p, h, err := tnet.exchange(ctx, server, q, time.Second*5)
|
||||
if err != nil {
|
||||
dnsErr := &net.DNSError{
|
||||
Err: err.Error(),
|
||||
Name: name,
|
||||
Server: server.String(),
|
||||
}
|
||||
if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
|
||||
dnsErr.IsTimeout = true
|
||||
}
|
||||
if _, ok := err.(*net.OpError); ok {
|
||||
dnsErr.IsTemporary = true
|
||||
}
|
||||
lastErr = dnsErr
|
||||
continue
|
||||
}
|
||||
|
||||
if err := checkHeader(&p, h); err != nil {
|
||||
dnsErr := &net.DNSError{
|
||||
Err: err.Error(),
|
||||
Name: name,
|
||||
Server: server.String(),
|
||||
}
|
||||
if err == errServerTemporarilyMisbehaving {
|
||||
dnsErr.IsTemporary = true
|
||||
}
|
||||
if err == errNoSuchHost {
|
||||
dnsErr.IsNotFound = true
|
||||
return p, server.String(), dnsErr
|
||||
}
|
||||
lastErr = dnsErr
|
||||
continue
|
||||
}
|
||||
|
||||
err = skipToAnswer(&p, qtype)
|
||||
if err == nil {
|
||||
return p, server.String(), nil
|
||||
}
|
||||
lastErr = &net.DNSError{
|
||||
Err: err.Error(),
|
||||
Name: name,
|
||||
Server: server.String(),
|
||||
}
|
||||
if err == errNoSuchHost {
|
||||
lastErr.(*net.DNSError).IsNotFound = true
|
||||
return p, server.String(), lastErr
|
||||
}
|
||||
}
|
||||
}
|
||||
return dnsmessage.Parser{}, "", lastErr
|
||||
}
|
||||
|
||||
func (tnet *Net) LookupContextHost(ctx context.Context, host string) ([]string, error) {
|
||||
if host == "" || (!tnet.hasV6 && !tnet.hasV4) {
|
||||
return nil, &net.DNSError{Err: errNoSuchHost.Error(), Name: host, IsNotFound: true}
|
||||
}
|
||||
zlen := len(host)
|
||||
if strings.IndexByte(host, ':') != -1 {
|
||||
if zidx := strings.LastIndexByte(host, '%'); zidx != -1 {
|
||||
zlen = zidx
|
||||
}
|
||||
}
|
||||
if ip, err := netip.ParseAddr(host[:zlen]); err == nil {
|
||||
return []string{ip.String()}, nil
|
||||
}
|
||||
|
||||
if !isDomainName(host) {
|
||||
return nil, &net.DNSError{Err: errNoSuchHost.Error(), Name: host, IsNotFound: true}
|
||||
}
|
||||
type result struct {
|
||||
p dnsmessage.Parser
|
||||
server string
|
||||
error
|
||||
}
|
||||
var addrsV4, addrsV6 []netip.Addr
|
||||
lanes := 0
|
||||
if tnet.hasV4 {
|
||||
lanes++
|
||||
}
|
||||
if tnet.hasV6 {
|
||||
lanes++
|
||||
}
|
||||
lane := make(chan result, lanes)
|
||||
var lastErr error
|
||||
if tnet.hasV4 {
|
||||
go func() {
|
||||
p, server, err := tnet.tryOneName(ctx, host+".", dnsmessage.TypeA)
|
||||
lane <- result{p, server, err}
|
||||
}()
|
||||
}
|
||||
if tnet.hasV6 {
|
||||
go func() {
|
||||
p, server, err := tnet.tryOneName(ctx, host+".", dnsmessage.TypeAAAA)
|
||||
lane <- result{p, server, err}
|
||||
}()
|
||||
}
|
||||
for l := 0; l < lanes; l++ {
|
||||
result := <-lane
|
||||
if result.error != nil {
|
||||
if lastErr == nil {
|
||||
lastErr = result.error
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
loop:
|
||||
for {
|
||||
h, err := result.p.AnswerHeader()
|
||||
if err != nil && err != dnsmessage.ErrSectionDone {
|
||||
lastErr = &net.DNSError{
|
||||
Err: errCannotMarshalDNSMessage.Error(),
|
||||
Name: host,
|
||||
Server: result.server,
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
switch h.Type {
|
||||
case dnsmessage.TypeA:
|
||||
a, err := result.p.AResource()
|
||||
if err != nil {
|
||||
lastErr = &net.DNSError{
|
||||
Err: errCannotMarshalDNSMessage.Error(),
|
||||
Name: host,
|
||||
Server: result.server,
|
||||
}
|
||||
break loop
|
||||
}
|
||||
addrsV4 = append(addrsV4, netip.AddrFrom4(a.A))
|
||||
|
||||
case dnsmessage.TypeAAAA:
|
||||
aaaa, err := result.p.AAAAResource()
|
||||
if err != nil {
|
||||
lastErr = &net.DNSError{
|
||||
Err: errCannotMarshalDNSMessage.Error(),
|
||||
Name: host,
|
||||
Server: result.server,
|
||||
}
|
||||
break loop
|
||||
}
|
||||
addrsV6 = append(addrsV6, netip.AddrFrom16(aaaa.AAAA))
|
||||
|
||||
default:
|
||||
if err := result.p.SkipAnswer(); err != nil {
|
||||
lastErr = &net.DNSError{
|
||||
Err: errCannotMarshalDNSMessage.Error(),
|
||||
Name: host,
|
||||
Server: result.server,
|
||||
}
|
||||
break loop
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
// We don't do RFC6724. Instead just put V6 addresses first if an IPv6 address is enabled
|
||||
var addrs []netip.Addr
|
||||
if tnet.hasV6 {
|
||||
addrs = append(addrsV6, addrsV4...)
|
||||
} else {
|
||||
addrs = append(addrsV4, addrsV6...)
|
||||
}
|
||||
|
||||
if len(addrs) == 0 && lastErr != nil {
|
||||
return nil, lastErr
|
||||
}
|
||||
saddrs := make([]string, 0, len(addrs))
|
||||
for _, ip := range addrs {
|
||||
saddrs = append(saddrs, ip.String())
|
||||
}
|
||||
return saddrs, nil
|
||||
}
|
||||
+108
-196
@@ -2,10 +2,6 @@ package wireguard
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
c "github.com/xtls/xray-core/common/ctx"
|
||||
@@ -14,246 +10,162 @@ import (
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/session"
|
||||
"github.com/xtls/xray-core/core"
|
||||
"github.com/xtls/xray-core/features/dns"
|
||||
"github.com/xtls/xray-core/features/policy"
|
||||
"github.com/xtls/xray-core/features/routing"
|
||||
"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/stat"
|
||||
"golang.zx2c4.com/wireguard/device"
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
var nullDestination = net.TCPDestination(net.AnyIP, 0)
|
||||
|
||||
type Server struct {
|
||||
conf *DeviceConfig
|
||||
ctx context.Context
|
||||
bindServer *netBindServer
|
||||
|
||||
info routingInfo
|
||||
policyManager policy.Manager
|
||||
dispatcher routing.Dispatcher
|
||||
}
|
||||
|
||||
tag string
|
||||
src net.Destination
|
||||
sniffingRequest session.SniffingRequest
|
||||
streamSettings *internet.MemoryStreamConfig
|
||||
uplinkCounter stats.Counter
|
||||
downlinkCounter stats.Counter
|
||||
|
||||
tun tun.Device
|
||||
stack *stack.Stack
|
||||
dev *device.Device
|
||||
mu sync.Mutex
|
||||
type routingInfo struct {
|
||||
ctx context.Context
|
||||
dispatcher routing.Dispatcher
|
||||
inboundTag *session.Inbound
|
||||
contentTag *session.Content
|
||||
}
|
||||
|
||||
func NewServer(ctx context.Context, conf *DeviceConfig) (*Server, error) {
|
||||
v := core.MustFromContext(ctx)
|
||||
p := v.GetFeature(policy.ManagerType()).(policy.Manager)
|
||||
d := v.GetFeature(routing.DispatcherType()).(routing.Dispatcher)
|
||||
|
||||
inbound := session.InboundFromContext(ctx)
|
||||
content := session.ContentFromContext(ctx)
|
||||
streamSettings := session.StreamSettingsFromContext(ctx).(*internet.MemoryStreamConfig)
|
||||
tag := inbound.Tag
|
||||
var uplinkCounter stats.Counter
|
||||
var downlinkCounter stats.Counter
|
||||
if len(tag) > 0 && p.ForSystem().Stats.InboundUplink {
|
||||
statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager)
|
||||
name := "inbound>>>" + tag + ">>>traffic>>>uplink"
|
||||
c, _ := stats.GetOrRegisterCounter(statsManager, name)
|
||||
if c != nil {
|
||||
uplinkCounter = c
|
||||
}
|
||||
}
|
||||
if len(tag) > 0 && p.ForSystem().Stats.InboundDownlink {
|
||||
statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager)
|
||||
name := "inbound>>>" + tag + ">>>traffic>>>downlink"
|
||||
c, _ := stats.GetOrRegisterCounter(statsManager, name)
|
||||
if c != nil {
|
||||
downlinkCounter = c
|
||||
}
|
||||
}
|
||||
|
||||
if len(conf.Peers) == 0 {
|
||||
return nil, errors.New("empty peers")
|
||||
}
|
||||
for _, peer := range conf.Peers {
|
||||
if peer.PublicKey == "" {
|
||||
return nil, errors.New("peer without publickey")
|
||||
}
|
||||
}
|
||||
|
||||
localAddresses := make([]netip.Addr, 0, len(conf.Endpoint))
|
||||
for _, localaddress := range conf.Endpoint {
|
||||
addr, err := netip.ParseAddr(localaddress)
|
||||
if err == nil {
|
||||
localAddresses = append(localAddresses, addr)
|
||||
continue
|
||||
}
|
||||
prefix, err := netip.ParsePrefix(localaddress)
|
||||
if err == nil {
|
||||
localAddresses = append(localAddresses, prefix.Addr())
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tun, _, stack, err := CreateNetTUN(localAddresses, nil, int(conf.Mtu), false)
|
||||
endpoints, hasIPv4, hasIPv6, err := parseEndpoints(conf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Server{
|
||||
conf: conf,
|
||||
ctx: core.ToBackgroundDetachedContext(ctx),
|
||||
policyManager: p,
|
||||
dispatcher: d,
|
||||
server := &Server{
|
||||
bindServer: &netBindServer{
|
||||
netBind: netBind{
|
||||
dns: v.GetFeature(dns.ClientType()).(dns.Client),
|
||||
dnsOption: dns.IPOption{
|
||||
IPv4Enable: hasIPv4,
|
||||
IPv6Enable: hasIPv6,
|
||||
},
|
||||
workers: int(conf.NumWorkers),
|
||||
readQueue: make(chan *netReadInfo),
|
||||
},
|
||||
},
|
||||
policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
|
||||
}
|
||||
|
||||
tag: inbound.Tag,
|
||||
src: inbound.Source,
|
||||
sniffingRequest: content.SniffingRequest,
|
||||
streamSettings: streamSettings,
|
||||
uplinkCounter: uplinkCounter,
|
||||
downlinkCounter: downlinkCounter,
|
||||
tun, err := conf.createTun()(endpoints, int(conf.Mtu), server.forwardConnection)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tun: tun,
|
||||
stack: stack,
|
||||
}, nil
|
||||
if err = tun.BuildDevice(createIPCRequest(conf), server.bindServer); err != nil {
|
||||
_ = tun.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return server, nil
|
||||
}
|
||||
|
||||
// Network implements proxy.Inbound.Network.
|
||||
// Network implements proxy.Inbound.
|
||||
func (*Server) Network() []net.Network {
|
||||
return []net.Network{}
|
||||
return []net.Network{net.Network_UDP}
|
||||
}
|
||||
|
||||
// Process implements proxy.Inbound.Process.
|
||||
// Process implements proxy.Inbound.
|
||||
func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Connection, dispatcher routing.Dispatcher) error {
|
||||
return nil
|
||||
}
|
||||
s.info = routingInfo{
|
||||
ctx: ctx,
|
||||
dispatcher: dispatcher,
|
||||
inboundTag: session.InboundFromContext(ctx),
|
||||
contentTag: session.ContentFromContext(ctx),
|
||||
}
|
||||
|
||||
// Close implements common.Closable.Close.
|
||||
func (s *Server) Close() error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.dev != nil {
|
||||
s.dev.Close()
|
||||
s.dev = nil
|
||||
s.tun = nil
|
||||
} else if s.tun != nil {
|
||||
s.tun.Close()
|
||||
s.tun = nil
|
||||
ep, err := s.bindServer.ParseEndpoint(conn.RemoteAddr().String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start implements common.Runnable.Start.
|
||||
func (s *Server) Start() error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.dev != nil {
|
||||
return nil
|
||||
}
|
||||
if s.src.Address.Family().IsDomain() {
|
||||
return errors.New("address is domain")
|
||||
}
|
||||
listenFunc := func() (net.PacketConn, error) {
|
||||
pktConn, err := internet.ListenSystemPacket(context.Background(), &net.UDPAddr{IP: s.src.Address.IP(), Port: int(s.src.Port)}, s.streamSettings.SocketSettings)
|
||||
nep := ep.(*netEndpoint)
|
||||
nep.conn = conn
|
||||
|
||||
reader := buf.NewPacketReader(conn)
|
||||
for {
|
||||
mb, err := reader.ReadMultiBuffer()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
nep.conn = nil
|
||||
buf.ReleaseMulti(mb)
|
||||
return 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)
|
||||
|
||||
for i, b := range mb {
|
||||
|
||||
rawBytes := b.Bytes()
|
||||
if b.Len() > 3 {
|
||||
rawBytes[1] = 0
|
||||
rawBytes[2] = 0
|
||||
rawBytes[3] = 0
|
||||
}
|
||||
pktConn = newConn
|
||||
}
|
||||
if s.uplinkCounter != nil || s.downlinkCounter != nil {
|
||||
pktConn = &PacketCounterConnection{
|
||||
PacketConn: pktConn,
|
||||
ReadCounter: s.uplinkCounter,
|
||||
WriteCounter: s.downlinkCounter,
|
||||
|
||||
select {
|
||||
case s.bindServer.readQueue <- &netReadInfo{
|
||||
buff: b,
|
||||
endpoint: nep,
|
||||
}:
|
||||
case <-s.bindServer.closedCh:
|
||||
nep.conn = nil
|
||||
buf.ReleaseMulti(mb[i:])
|
||||
return errors.New("bind closed")
|
||||
}
|
||||
}
|
||||
return pktConn, nil
|
||||
}
|
||||
bind := &bind{
|
||||
listenFunc: listenFunc,
|
||||
}
|
||||
logger := &device.Logger{
|
||||
Verbosef: func(format string, args ...any) {
|
||||
log.Record(&log.GeneralMessage{
|
||||
Severity: log.Severity_Debug,
|
||||
Content: fmt.Sprintf(format, args...),
|
||||
})
|
||||
},
|
||||
Errorf: func(format string, args ...any) {
|
||||
log.Record(&log.GeneralMessage{
|
||||
Severity: log.Severity_Error,
|
||||
Content: fmt.Sprintf(format, args...),
|
||||
})
|
||||
},
|
||||
}
|
||||
dev := device.NewDevice(s.tun, bind, logger)
|
||||
var cfg strings.Builder
|
||||
cfg.WriteString("private_key=" + s.conf.SecretKey + "\n")
|
||||
for _, peer := range s.conf.Peers {
|
||||
cfg.WriteString("public_key=" + peer.PublicKey + "\n")
|
||||
if peer.PreSharedKey != "" {
|
||||
cfg.WriteString("preshared_key=" + peer.PreSharedKey + "\n")
|
||||
}
|
||||
for _, ip := range peer.AllowedIps {
|
||||
cfg.WriteString("allowed_ip=" + ip + "\n")
|
||||
}
|
||||
if peer.KeepAlive != "" {
|
||||
cfg.WriteString("persistent_keepalive_interval=" + peer.KeepAlive + "\n")
|
||||
}
|
||||
}
|
||||
err := dev.IpcSet(cfg.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = dev.Up()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.dev = dev
|
||||
createForwarder(s.stack, s.HandleConnection)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) HandleConnection(conn net.Conn, dest net.Destination) {
|
||||
defer conn.Close()
|
||||
ctx, cancel := context.WithCancel(s.ctx)
|
||||
defer cancel()
|
||||
ctx = c.ContextWithID(ctx, session.NewID())
|
||||
|
||||
source := net.DestinationFromAddr(conn.RemoteAddr())
|
||||
inbound := session.Inbound{
|
||||
Name: "wireguard",
|
||||
Tag: s.tag,
|
||||
CanSpliceCopy: 3,
|
||||
Source: source,
|
||||
func (s *Server) forwardConnection(dest net.Destination, conn net.Conn) {
|
||||
if s.info.dispatcher == nil {
|
||||
errors.LogError(s.info.ctx, "unexpected: dispatcher == nil")
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(core.ToBackgroundDetachedContext(s.info.ctx))
|
||||
sid := session.NewID()
|
||||
ctx = c.ContextWithID(ctx, sid)
|
||||
inbound := session.Inbound{} // since promiscuousModeHandler mixed-up context, we shallow copy inbound (tag) and content (configs)
|
||||
if s.info.inboundTag != nil {
|
||||
inbound = *s.info.inboundTag
|
||||
}
|
||||
inbound.Name = "wireguard"
|
||||
inbound.CanSpliceCopy = 3
|
||||
|
||||
// overwrite the source to use the tun address for each sub context.
|
||||
// Since gvisor.ForwarderRequest doesn't provide any info to associate the sub-context with the Parent context
|
||||
// Currently we have no way to link to the original source address
|
||||
inbound.Source = net.DestinationFromAddr(conn.RemoteAddr())
|
||||
ctx = session.ContextWithInbound(ctx, &inbound)
|
||||
ctx = session.ContextWithContent(ctx, &session.Content{
|
||||
SniffingRequest: s.sniffingRequest,
|
||||
})
|
||||
content := new(session.Content)
|
||||
if s.info.contentTag != nil {
|
||||
content.SniffingRequest = s.info.contentTag.SniffingRequest
|
||||
}
|
||||
ctx = session.ContextWithContent(ctx, content)
|
||||
ctx = session.SubContextFromMuxInbound(ctx)
|
||||
|
||||
ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{
|
||||
From: inbound.Source,
|
||||
From: nullDestination,
|
||||
To: dest,
|
||||
Status: log.AccessAccepted,
|
||||
Reason: "",
|
||||
})
|
||||
errors.LogInfo(ctx, "processing from ", source, " to ", dest)
|
||||
|
||||
link := &transport.Link{
|
||||
Reader: &buf.TimeoutWrapperReader{Reader: buf.NewReader(conn)},
|
||||
err := s.info.dispatcher.DispatchLink(ctx, dest, &transport.Link{
|
||||
Reader: buf.NewReader(conn),
|
||||
Writer: buf.NewWriter(conn),
|
||||
})
|
||||
if err != nil {
|
||||
errors.LogInfoInner(ctx, err, "connection ends")
|
||||
}
|
||||
if err := s.dispatcher.DispatchLink(ctx, dest, link); err != nil {
|
||||
errors.LogError(ctx, errors.New("connection closed").Base(err))
|
||||
}
|
||||
|
||||
cancel()
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package wireguard_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime/debug"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/xtls/xray-core/core"
|
||||
"github.com/xtls/xray-core/proxy/wireguard"
|
||||
)
|
||||
|
||||
// TestWireGuardServerInitializationError verifies that an error during TUN initialization
|
||||
// (triggered by an empty SecretKey) in the WireGuard server does not cause a panic and returns an error instead.
|
||||
func TestWireGuardServerInitializationError(t *testing.T) {
|
||||
// Create a minimal core instance with default features
|
||||
config := &core.Config{}
|
||||
instance, err := core.New(config)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create core instance: %v", err)
|
||||
}
|
||||
// Set the Xray instance in the context
|
||||
ctx := context.WithValue(context.Background(), core.XrayKey(1), instance)
|
||||
|
||||
// Define the server configuration with an empty SecretKey to trigger error
|
||||
conf := &wireguard.DeviceConfig{
|
||||
IsClient: false,
|
||||
Endpoint: []string{"10.0.0.1/32"},
|
||||
Mtu: 1420,
|
||||
SecretKey: "", // Empty SecretKey to trigger error
|
||||
Peers: []*wireguard.PeerConfig{
|
||||
{
|
||||
PublicKey: "some_public_key",
|
||||
AllowedIps: []string{"10.0.0.2/32"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Use defer to catch any panic and fail the test explicitly
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Errorf("TUN initialization panicked: %v", r)
|
||||
debug.PrintStack()
|
||||
}
|
||||
}()
|
||||
|
||||
// Attempt to initialize the WireGuard server
|
||||
_, err = wireguard.NewServer(ctx, conf)
|
||||
|
||||
// Check that an error is returned
|
||||
assert.ErrorContains(t, err, "failed to set private_key: hex string does not fit the slice")
|
||||
}
|
||||
+161
-65
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/netip"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -12,7 +13,9 @@ import (
|
||||
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/log"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/proxy/wireguard/gvisortun"
|
||||
"gvisor.dev/gvisor/pkg/buffer"
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
|
||||
@@ -22,8 +25,77 @@ import (
|
||||
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
|
||||
"gvisor.dev/gvisor/pkg/waiter"
|
||||
|
||||
"golang.zx2c4.com/wireguard/conn"
|
||||
"golang.zx2c4.com/wireguard/device"
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
)
|
||||
|
||||
type tunCreator func(localAddresses []netip.Addr, mtu int, handler promiscuousModeHandler) (Tunnel, error)
|
||||
|
||||
type promiscuousModeHandler func(dest net.Destination, conn net.Conn)
|
||||
|
||||
type Tunnel interface {
|
||||
BuildDevice(ipc string, bind conn.Bind) error
|
||||
DialContextTCPAddrPort(ctx context.Context, addr netip.AddrPort) (net.Conn, error)
|
||||
DialUDPAddrPort(laddr, raddr netip.AddrPort) (net.Conn, error)
|
||||
Close() error
|
||||
}
|
||||
|
||||
type tunnel struct {
|
||||
tun tun.Device
|
||||
device *device.Device
|
||||
rw sync.Mutex
|
||||
}
|
||||
|
||||
func (t *tunnel) BuildDevice(ipc string, bind conn.Bind) (err error) {
|
||||
t.rw.Lock()
|
||||
defer t.rw.Unlock()
|
||||
|
||||
if t.device != nil {
|
||||
return errors.New("device is already initialized")
|
||||
}
|
||||
|
||||
logger := &device.Logger{
|
||||
Verbosef: func(format string, args ...any) {
|
||||
log.Record(&log.GeneralMessage{
|
||||
Severity: log.Severity_Debug,
|
||||
Content: fmt.Sprintf(format, args...),
|
||||
})
|
||||
},
|
||||
Errorf: func(format string, args ...any) {
|
||||
log.Record(&log.GeneralMessage{
|
||||
Severity: log.Severity_Error,
|
||||
Content: fmt.Sprintf(format, args...),
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
t.device = device.NewDevice(t.tun, bind, logger)
|
||||
if err = t.device.IpcSet(ipc); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = t.device.Up(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *tunnel) Close() (err error) {
|
||||
t.rw.Lock()
|
||||
defer t.rw.Unlock()
|
||||
|
||||
if t.device == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
t.device.Close()
|
||||
t.device = nil
|
||||
err = t.tun.Close()
|
||||
t.tun = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func CalculateInterfaceName(name string) (tunName string) {
|
||||
if runtime.GOOS == "darwin" {
|
||||
tunName = "utun"
|
||||
@@ -49,61 +121,93 @@ func CalculateInterfaceName(name string) (tunName string) {
|
||||
return
|
||||
}
|
||||
|
||||
func createForwarder(gstack *stack.Stack, handler func(conn net.Conn, dest net.Destination)) {
|
||||
gstack.SetPromiscuousMode(1, true)
|
||||
gstack.SetSpoofing(1, true)
|
||||
var _ Tunnel = (*gvisorNet)(nil)
|
||||
|
||||
tcpForwarder := tcp.NewForwarder(gstack, 0, 65535, func(r *tcp.ForwarderRequest) {
|
||||
go func(r *tcp.ForwarderRequest) {
|
||||
var wq waiter.Queue
|
||||
id := r.ID()
|
||||
type gvisorNet struct {
|
||||
tunnel
|
||||
net *gvisortun.Net
|
||||
}
|
||||
|
||||
ep, err := r.CreateEndpoint(&wq)
|
||||
if err != nil {
|
||||
errors.LogError(context.Background(), err.String())
|
||||
r.Complete(true)
|
||||
return
|
||||
}
|
||||
func (g *gvisorNet) Close() error {
|
||||
return g.tunnel.Close()
|
||||
}
|
||||
|
||||
options := ep.SocketOptions()
|
||||
options.SetKeepAlive(false)
|
||||
options.SetReuseAddress(true)
|
||||
options.SetReusePort(true)
|
||||
func (g *gvisorNet) DialContextTCPAddrPort(ctx context.Context, addr netip.AddrPort) (
|
||||
net.Conn, error,
|
||||
) {
|
||||
return g.net.DialContextTCPAddrPort(ctx, addr)
|
||||
}
|
||||
|
||||
handler(gonet.NewTCPConn(&wq, ep), net.TCPDestination(net.IPAddress(id.LocalAddress.AsSlice()), net.Port(id.LocalPort)))
|
||||
func (g *gvisorNet) DialUDPAddrPort(laddr, raddr netip.AddrPort) (net.Conn, error) {
|
||||
return g.net.DialUDPAddrPort(laddr, raddr)
|
||||
}
|
||||
|
||||
ep.Close()
|
||||
r.Complete(false)
|
||||
}(r)
|
||||
})
|
||||
gstack.SetTransportProtocolHandler(tcp.ProtocolNumber, tcpForwarder.HandlePacket)
|
||||
|
||||
manager := &udpManager{
|
||||
stack: gstack,
|
||||
handler: handler,
|
||||
m: make(map[string]*udpConn),
|
||||
func createGVisorTun(localAddresses []netip.Addr, mtu int, handler promiscuousModeHandler) (Tunnel, error) {
|
||||
out := &gvisorNet{}
|
||||
tun, n, gstack, err := gvisortun.CreateNetTUN(localAddresses, mtu, handler != nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gstack.SetTransportProtocolHandler(udp.ProtocolNumber, func(id stack.TransportEndpointID, pkt *stack.PacketBuffer) bool {
|
||||
data := pkt.Clone().Data().AsRange().ToSlice()
|
||||
// if len(data) == 0 {
|
||||
// return false
|
||||
// }
|
||||
srcIP := net.IPAddress(id.RemoteAddress.AsSlice())
|
||||
dstIP := net.IPAddress(id.LocalAddress.AsSlice())
|
||||
if srcIP == nil || dstIP == nil {
|
||||
panic(id)
|
||||
if handler != nil {
|
||||
// handler is only used for promiscuous mode
|
||||
// capture all packets and send to handler
|
||||
|
||||
tcpForwarder := tcp.NewForwarder(gstack, 0, 65535, func(r *tcp.ForwarderRequest) {
|
||||
go func(r *tcp.ForwarderRequest) {
|
||||
var wq waiter.Queue
|
||||
id := r.ID()
|
||||
|
||||
ep, err := r.CreateEndpoint(&wq)
|
||||
if err != nil {
|
||||
errors.LogError(context.Background(), err.String())
|
||||
r.Complete(true)
|
||||
return
|
||||
}
|
||||
|
||||
options := ep.SocketOptions()
|
||||
options.SetKeepAlive(false)
|
||||
options.SetReuseAddress(true)
|
||||
options.SetReusePort(true)
|
||||
|
||||
handler(net.TCPDestination(net.IPAddress(id.LocalAddress.AsSlice()), net.Port(id.LocalPort)), gonet.NewTCPConn(&wq, ep))
|
||||
|
||||
ep.Close()
|
||||
r.Complete(false)
|
||||
}(r)
|
||||
})
|
||||
gstack.SetTransportProtocolHandler(tcp.ProtocolNumber, tcpForwarder.HandlePacket)
|
||||
|
||||
manager := &udpManager{
|
||||
stack: gstack,
|
||||
handler: handler,
|
||||
m: make(map[string]*udpConn),
|
||||
}
|
||||
src := net.UDPDestination(srcIP, net.Port(id.RemotePort))
|
||||
dst := net.UDPDestination(dstIP, net.Port(id.LocalPort))
|
||||
manager.feed(src, dst, data)
|
||||
return true
|
||||
})
|
||||
|
||||
gstack.SetTransportProtocolHandler(udp.ProtocolNumber, func(id stack.TransportEndpointID, pkt *stack.PacketBuffer) bool {
|
||||
data := pkt.Clone().Data().AsRange().ToSlice()
|
||||
// if len(data) == 0 {
|
||||
// return false
|
||||
// }
|
||||
srcIP := net.IPAddress(id.RemoteAddress.AsSlice())
|
||||
dstIP := net.IPAddress(id.LocalAddress.AsSlice())
|
||||
if srcIP == nil || dstIP == nil {
|
||||
panic(id)
|
||||
}
|
||||
src := net.UDPDestination(srcIP, net.Port(id.RemotePort))
|
||||
dst := net.UDPDestination(dstIP, net.Port(id.LocalPort))
|
||||
manager.feed(src, dst, data)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
out.tun, out.net = tun, n
|
||||
return out, nil
|
||||
}
|
||||
|
||||
type udpManager struct {
|
||||
stack *stack.Stack
|
||||
handler func(conn net.Conn, dest net.Destination)
|
||||
handler func(dest net.Destination, conn net.Conn)
|
||||
m map[string]*udpConn
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
@@ -142,7 +246,7 @@ func (m *udpManager) feed(src net.Destination, dst net.Destination, data []byte)
|
||||
m.mutex.Unlock()
|
||||
}
|
||||
m.m[src.NetAddr()] = uc
|
||||
go m.handler(uc, dst)
|
||||
go m.handler(dst, uc)
|
||||
}
|
||||
|
||||
select {
|
||||
@@ -241,23 +345,19 @@ type udpConn struct {
|
||||
}
|
||||
|
||||
func (c *udpConn) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
||||
for {
|
||||
q, ok := <-c.queue
|
||||
if !ok {
|
||||
return nil, io.EOF
|
||||
}
|
||||
|
||||
b := buf.New()
|
||||
if _, err := b.Write(q.p); err != nil {
|
||||
errors.LogErrorInner(context.Background(), err, "drop packet to ", q.dest, " with size ", len(q.p))
|
||||
b.Release()
|
||||
continue
|
||||
}
|
||||
|
||||
b.UDP = q.dest
|
||||
|
||||
return buf.MultiBuffer{b}, nil
|
||||
q, ok := <-c.queue
|
||||
if !ok {
|
||||
return nil, io.EOF
|
||||
}
|
||||
|
||||
b := buf.New()
|
||||
if _, err := b.Write(q.p); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b.UDP = q.dest
|
||||
|
||||
return buf.MultiBuffer{b}, nil
|
||||
}
|
||||
|
||||
func (c *udpConn) Read(p []byte) (int, error) {
|
||||
@@ -276,11 +376,7 @@ func (c *udpConn) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
||||
for i, b := range mb {
|
||||
dst := c.dst
|
||||
if b.UDP != nil {
|
||||
if b.UDP.Address.Family().IsDomain() {
|
||||
errors.LogError(context.Background(), "impossible domain packet ", b.UDP, " reply via original target ", dst)
|
||||
} else {
|
||||
dst = *b.UDP
|
||||
}
|
||||
dst = *b.UDP
|
||||
}
|
||||
err := c.writeFunc(b.Bytes(), dst, c.src)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
//go:build !linux
|
||||
//go:build !linux || android
|
||||
|
||||
package wireguard
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/netip"
|
||||
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
)
|
||||
|
||||
func createKernelTun([]netip.Addr, []netip.Addr, int) (tdev tun.Device, tnet *Net, err error) {
|
||||
return nil, nil, errors.New("not implemented")
|
||||
func createKernelTun(localAddresses []netip.Addr, mtu int, handler promiscuousModeHandler) (t Tunnel, err error) {
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func KernelTunSupported() (bool, error) {
|
||||
|
||||
+123
-113
@@ -1,4 +1,4 @@
|
||||
//go:build linux
|
||||
//go:build linux && !android
|
||||
|
||||
package wireguard
|
||||
|
||||
@@ -20,6 +20,17 @@ import (
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
)
|
||||
|
||||
type deviceNet struct {
|
||||
tunnel
|
||||
dialer *net.Dialer
|
||||
lc *net.ListenConfig
|
||||
|
||||
handle *netlink.Handle
|
||||
linkAddrs []netlink.Addr
|
||||
routes []*netlink.Route
|
||||
rules []*netlink.Rule
|
||||
}
|
||||
|
||||
var (
|
||||
tableIndex int = 10230
|
||||
mu sync.Mutex
|
||||
@@ -37,18 +48,82 @@ func allocateIPv6TableIndex() int {
|
||||
return currentIndex
|
||||
}
|
||||
|
||||
type kernelTun struct {
|
||||
tun.Device
|
||||
|
||||
dialer *net.Dialer
|
||||
lc *net.ListenConfig
|
||||
handle *netlink.Handle
|
||||
linkAddrs []netlink.Addr
|
||||
routes []*netlink.Route
|
||||
rules []*netlink.Rule
|
||||
func newDeviceNet(interfaceName string) *deviceNet {
|
||||
dialer := &net.Dialer{}
|
||||
dialer.Control = func(network, address string, c syscall.RawConn) error {
|
||||
return c.Control(func(fd uintptr) {
|
||||
if err := syscall.BindToDevice(int(fd), interfaceName); err != nil {
|
||||
errors.LogInfoInner(context.Background(), err, "failed to bind to device")
|
||||
}
|
||||
})
|
||||
}
|
||||
lc := &net.ListenConfig{}
|
||||
lc.Control = func(network, address string, c syscall.RawConn) error {
|
||||
return c.Control(func(fd uintptr) {
|
||||
if err := syscall.BindToDevice(int(fd), interfaceName); err != nil {
|
||||
errors.LogInfoInner(context.Background(), err, "failed to bind to device")
|
||||
}
|
||||
})
|
||||
}
|
||||
return &deviceNet{dialer: dialer, lc: lc}
|
||||
}
|
||||
|
||||
func createKernelTun(localAddresses, dnsServers []netip.Addr, mtu int) (tdev tun.Device, tnet *Net, err error) {
|
||||
func (d *deviceNet) DialContextTCPAddrPort(ctx context.Context, addr netip.AddrPort) (
|
||||
net.Conn, error,
|
||||
) {
|
||||
return d.dialer.DialContext(ctx, "tcp", addr.String())
|
||||
}
|
||||
|
||||
func (d *deviceNet) DialUDPAddrPort(laddr, raddr netip.AddrPort) (net.Conn, error) {
|
||||
var conn net.PacketConn
|
||||
var err error
|
||||
if raddr.Addr().Is4() {
|
||||
conn, err = d.lc.ListenPacket(context.Background(), "udp", "0.0.0.0:0")
|
||||
} else {
|
||||
conn, err = d.lc.ListenPacket(context.Background(), "udp", "[::]:0")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &internet.PacketConnWrapper{
|
||||
PacketConn: conn,
|
||||
Dest: &net.UDPAddr{
|
||||
IP: raddr.Addr().AsSlice(),
|
||||
Port: int(raddr.Port()),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *deviceNet) Close() (err error) {
|
||||
var errs []error
|
||||
for _, rule := range d.rules {
|
||||
if err = d.handle.RuleDel(rule); err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed to delete rule: %w", err))
|
||||
}
|
||||
}
|
||||
for _, route := range d.routes {
|
||||
if err = d.handle.RouteDel(route); err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed to delete route: %w", err))
|
||||
}
|
||||
}
|
||||
if err = d.tunnel.Close(); err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed to close tunnel: %w", err))
|
||||
}
|
||||
if d.handle != nil {
|
||||
d.handle.Close()
|
||||
d.handle = nil
|
||||
}
|
||||
if len(errs) == 0 {
|
||||
return nil
|
||||
}
|
||||
return goerrors.Join(errs...)
|
||||
}
|
||||
|
||||
func createKernelTun(localAddresses []netip.Addr, mtu int, handler promiscuousModeHandler) (t Tunnel, err error) {
|
||||
if handler != nil {
|
||||
return nil, errors.New("TODO: support promiscuous mode")
|
||||
}
|
||||
|
||||
var v4, v6 *netip.Addr
|
||||
for _, prefixes := range localAddresses {
|
||||
if v4 == nil && prefixes.Is4() {
|
||||
@@ -75,22 +150,22 @@ func createKernelTun(localAddresses, dnsServers []netip.Addr, mtu int) (tdev tun
|
||||
// system configs.
|
||||
if v4 != nil {
|
||||
if err = writeSysctlZero("/proc/sys/net/ipv4/conf/all/rp_filter"); err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to disable ipv4 rp_filter for all: %w", err)
|
||||
return nil, fmt.Errorf("failed to disable ipv4 rp_filter for all: %w", err)
|
||||
}
|
||||
}
|
||||
if v6 != nil {
|
||||
if err = writeSysctlZero("/proc/sys/net/ipv6/conf/all/disable_ipv6"); err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to enable ipv6: %w", err)
|
||||
return nil, fmt.Errorf("failed to enable ipv6: %w", err)
|
||||
}
|
||||
if err = writeSysctlZero("/proc/sys/net/ipv6/conf/all/rp_filter"); err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to disable ipv6 rp_filter for all: %w", err)
|
||||
return nil, fmt.Errorf("failed to disable ipv6 rp_filter for all: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
n := CalculateInterfaceName("wg")
|
||||
wgt, err := tun.CreateTUN(n, mtu)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
@@ -102,12 +177,12 @@ func createKernelTun(localAddresses, dnsServers []netip.Addr, mtu int) (tdev tun
|
||||
// the operation require root privilege on container require '--privileged' flag.
|
||||
if v4 != nil {
|
||||
if err = writeSysctlZero("/proc/sys/net/ipv4/conf/" + n + "/rp_filter"); err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to disable ipv4 rp_filter for tunnel: %w", err)
|
||||
return nil, fmt.Errorf("failed to disable ipv4 rp_filter for tunnel: %w", err)
|
||||
}
|
||||
}
|
||||
if v6 != nil {
|
||||
if err = writeSysctlZero("/proc/sys/net/ipv6/conf/" + n + "/rp_filter"); err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to disable ipv6 rp_filter for tunnel: %w", err)
|
||||
return nil, fmt.Errorf("failed to disable ipv6 rp_filter for tunnel: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,28 +196,25 @@ func createKernelTun(localAddresses, dnsServers []netip.Addr, mtu int) (tdev tun
|
||||
}
|
||||
ipv6TableIndex--
|
||||
if ipv6TableIndex < 0 {
|
||||
return nil, nil, fmt.Errorf("failed to find available ipv6 table index")
|
||||
return nil, fmt.Errorf("failed to find available ipv6 table index")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
t := &kernelTun{
|
||||
Device: wgt,
|
||||
}
|
||||
|
||||
t.handle, err = netlink.NewHandle()
|
||||
out := newDeviceNet(n)
|
||||
out.handle, err = netlink.NewHandle()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
t.Close()
|
||||
_ = out.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
l, err := netlink.LinkByName(n)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if v4 != nil {
|
||||
@@ -152,7 +224,7 @@ func createKernelTun(localAddresses, dnsServers []netip.Addr, mtu int) (tdev tun
|
||||
Mask: net.CIDRMask(v4.BitLen(), v4.BitLen()),
|
||||
},
|
||||
}
|
||||
t.linkAddrs = append(t.linkAddrs, addr)
|
||||
out.linkAddrs = append(out.linkAddrs, addr)
|
||||
}
|
||||
if v6 != nil {
|
||||
addr := netlink.Addr{
|
||||
@@ -161,7 +233,7 @@ func createKernelTun(localAddresses, dnsServers []netip.Addr, mtu int) (tdev tun
|
||||
Mask: net.CIDRMask(v6.BitLen(), v6.BitLen()),
|
||||
},
|
||||
}
|
||||
t.linkAddrs = append(t.linkAddrs, addr)
|
||||
out.linkAddrs = append(out.linkAddrs, addr)
|
||||
|
||||
rt := &netlink.Route{
|
||||
LinkIndex: l.Attrs().Index,
|
||||
@@ -171,102 +243,40 @@ func createKernelTun(localAddresses, dnsServers []netip.Addr, mtu int) (tdev tun
|
||||
},
|
||||
Table: ipv6TableIndex,
|
||||
}
|
||||
t.routes = append(t.routes, rt)
|
||||
out.routes = append(out.routes, rt)
|
||||
|
||||
r := netlink.NewRule()
|
||||
r.Table, r.Family, r.Src = ipv6TableIndex, unix.AF_INET6, addr.IPNet
|
||||
t.rules = append(t.rules, r)
|
||||
out.rules = append(out.rules, r)
|
||||
r = netlink.NewRule()
|
||||
r.Table, r.Family, r.OifName = ipv6TableIndex, unix.AF_INET6, n
|
||||
t.rules = append(t.rules, r)
|
||||
out.rules = append(out.rules, r)
|
||||
}
|
||||
|
||||
for _, addr := range t.linkAddrs {
|
||||
if err = t.handle.AddrAdd(l, &addr); err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to add address %s to %s: %w", addr, n, err)
|
||||
for _, addr := range out.linkAddrs {
|
||||
if err = out.handle.AddrAdd(l, &addr); err != nil {
|
||||
return nil, fmt.Errorf("failed to add address %s to %s: %w", addr, n, err)
|
||||
}
|
||||
}
|
||||
if err = t.handle.LinkSetMTU(l, mtu); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err = t.handle.LinkSetUp(l); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
for _, route := range t.routes {
|
||||
if err = t.handle.RouteAdd(route); err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to add route %s: %w", route, err)
|
||||
}
|
||||
}
|
||||
for _, rule := range t.rules {
|
||||
if err = t.handle.RuleAdd(rule); err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to add rule %s: %w", rule, err)
|
||||
}
|
||||
}
|
||||
|
||||
dialer := &net.Dialer{}
|
||||
dialer.Control = func(network, address string, c syscall.RawConn) error {
|
||||
return c.Control(func(fd uintptr) {
|
||||
if err := syscall.BindToDevice(int(fd), n); err != nil {
|
||||
errors.LogInfoInner(context.Background(), err, "failed to bind to device")
|
||||
}
|
||||
})
|
||||
}
|
||||
lc := &net.ListenConfig{}
|
||||
lc.Control = func(network, address string, c syscall.RawConn) error {
|
||||
return c.Control(func(fd uintptr) {
|
||||
if err := syscall.BindToDevice(int(fd), n); err != nil {
|
||||
errors.LogInfoInner(context.Background(), err, "failed to bind to device")
|
||||
}
|
||||
})
|
||||
}
|
||||
t.dialer = dialer
|
||||
t.lc = lc
|
||||
|
||||
tnet = &Net{
|
||||
DialContextTCPAddrPort: t.DialContextTCPAddrPort,
|
||||
DialUDPAddrPort: t.DialUDPAddrPort,
|
||||
dnsServers: dnsServers,
|
||||
hasV4: v4 != nil,
|
||||
hasV6: v6 != nil,
|
||||
}
|
||||
|
||||
return t, tnet, nil
|
||||
}
|
||||
|
||||
func (tun *kernelTun) Close() (err error) {
|
||||
var errs []error
|
||||
for _, rule := range tun.rules {
|
||||
if err = tun.handle.RuleDel(rule); err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed to delete rule: %w", err))
|
||||
}
|
||||
}
|
||||
for _, route := range tun.routes {
|
||||
if err = tun.handle.RouteDel(route); err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed to delete route: %w", err))
|
||||
}
|
||||
}
|
||||
if err = tun.Device.Close(); err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed to close device: %w", err))
|
||||
}
|
||||
tun.handle.Close()
|
||||
errs = append(errs, tun.Device.Close())
|
||||
return goerrors.Join(errs...)
|
||||
}
|
||||
|
||||
func (tun *kernelTun) DialContextTCPAddrPort(ctx context.Context, addr netip.AddrPort) (net.Conn, error) {
|
||||
return tun.dialer.DialContext(ctx, "tcp", addr.String())
|
||||
}
|
||||
|
||||
func (tun *kernelTun) DialUDPAddrPort(laddr, raddr netip.AddrPort) (net.Conn, error) {
|
||||
conn, err := tun.lc.ListenPacket(context.Background(), "udp", ":0")
|
||||
if err != nil {
|
||||
if err = out.handle.LinkSetMTU(l, mtu); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &internet.PacketConnWrapper{
|
||||
PacketConn: conn,
|
||||
Dest: net.UDPAddrFromAddrPort(raddr),
|
||||
}, nil
|
||||
if err = out.handle.LinkSetUp(l); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, route := range out.routes {
|
||||
if err = out.handle.RouteAdd(route); err != nil {
|
||||
return nil, fmt.Errorf("failed to add route %s: %w", route, err)
|
||||
}
|
||||
}
|
||||
for _, rule := range out.rules {
|
||||
if err = out.handle.RuleAdd(rule); err != nil {
|
||||
return nil, fmt.Errorf("failed to add rule %s: %w", rule, err)
|
||||
}
|
||||
}
|
||||
out.tun = wgt
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func KernelTunSupported() (bool, error) {
|
||||
|
||||
@@ -2,6 +2,10 @@ package wireguard
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"strings"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
)
|
||||
@@ -10,9 +14,80 @@ func init() {
|
||||
common.Must(common.RegisterConfig((*DeviceConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
||||
deviceConfig := config.(*DeviceConfig)
|
||||
if deviceConfig.IsClient {
|
||||
return NewClient(ctx, deviceConfig)
|
||||
return New(ctx, deviceConfig)
|
||||
} else {
|
||||
return NewServer(ctx, deviceConfig)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
// convert endpoint string to netip.Addr
|
||||
func parseEndpoints(conf *DeviceConfig) ([]netip.Addr, bool, bool, error) {
|
||||
var hasIPv4, hasIPv6 bool
|
||||
|
||||
endpoints := make([]netip.Addr, len(conf.Endpoint))
|
||||
for i, str := range conf.Endpoint {
|
||||
var addr netip.Addr
|
||||
if strings.Contains(str, "/") {
|
||||
prefix, err := netip.ParsePrefix(str)
|
||||
if err != nil {
|
||||
return nil, false, false, err
|
||||
}
|
||||
addr = prefix.Addr()
|
||||
if prefix.Bits() != addr.BitLen() {
|
||||
return nil, false, false, errors.New("interface address subnet should be /32 for IPv4 and /128 for IPv6")
|
||||
}
|
||||
} else {
|
||||
var err error
|
||||
addr, err = netip.ParseAddr(str)
|
||||
if err != nil {
|
||||
return nil, false, false, err
|
||||
}
|
||||
}
|
||||
endpoints[i] = addr
|
||||
|
||||
if addr.Is4() {
|
||||
hasIPv4 = true
|
||||
} else if addr.Is6() {
|
||||
hasIPv6 = true
|
||||
}
|
||||
}
|
||||
|
||||
return endpoints, hasIPv4, hasIPv6, nil
|
||||
}
|
||||
|
||||
// serialize the config into an IPC request
|
||||
func createIPCRequest(conf *DeviceConfig) string {
|
||||
var request strings.Builder
|
||||
|
||||
request.WriteString(fmt.Sprintf("private_key=%s\n", conf.SecretKey))
|
||||
|
||||
if !conf.IsClient {
|
||||
// placeholder, we'll handle actual port listening on Xray
|
||||
request.WriteString("listen_port=1337\n")
|
||||
}
|
||||
|
||||
for _, peer := range conf.Peers {
|
||||
if peer.PublicKey != "" {
|
||||
request.WriteString(fmt.Sprintf("public_key=%s\n", peer.PublicKey))
|
||||
}
|
||||
|
||||
if peer.PreSharedKey != "" {
|
||||
request.WriteString(fmt.Sprintf("preshared_key=%s\n", peer.PreSharedKey))
|
||||
}
|
||||
|
||||
if peer.Endpoint != "" {
|
||||
request.WriteString(fmt.Sprintf("endpoint=%s\n", peer.Endpoint))
|
||||
}
|
||||
|
||||
for _, ip := range peer.AllowedIps {
|
||||
request.WriteString(fmt.Sprintf("allowed_ip=%s\n", ip))
|
||||
}
|
||||
|
||||
if peer.KeepAlive != 0 {
|
||||
request.WriteString(fmt.Sprintf("persistent_keepalive_interval=%d\n", peer.KeepAlive))
|
||||
}
|
||||
}
|
||||
|
||||
return request.String()[:request.Len()]
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package finalmask
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"slices"
|
||||
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
@@ -29,7 +28,7 @@ func NewUdpmaskManager(udpmasks []Udpmask) *UdpmaskManager {
|
||||
func (m *UdpmaskManager) WrapPacketConnClient(raw net.PacketConn) (net.PacketConn, error) {
|
||||
var sizes []int
|
||||
var conns []net.PacketConn
|
||||
for i, mask := range slices.Backward(m.udpmasks) {
|
||||
for i, mask := range m.udpmasks {
|
||||
if _, ok := mask.(headerConn); ok {
|
||||
conn, err := mask.WrapPacketConnClient(nil, i, len(m.udpmasks)-1)
|
||||
if err != nil {
|
||||
@@ -62,7 +61,7 @@ func (m *UdpmaskManager) WrapPacketConnClient(raw net.PacketConn) (net.PacketCon
|
||||
func (m *UdpmaskManager) WrapPacketConnServer(raw net.PacketConn) (net.PacketConn, error) {
|
||||
var sizes []int
|
||||
var conns []net.PacketConn
|
||||
for i, mask := range slices.Backward(m.udpmasks) {
|
||||
for i, mask := range m.udpmasks {
|
||||
if _, ok := mask.(headerConn); ok {
|
||||
conn, err := mask.WrapPacketConnServer(nil, i, len(m.udpmasks)-1)
|
||||
if err != nil {
|
||||
@@ -125,7 +124,7 @@ func (c *headerManagerConn) ReadFrom(p []byte) (n int, addr net.Addr, err error)
|
||||
if err != nil {
|
||||
return n, addr, err
|
||||
}
|
||||
buf := b[:n]
|
||||
b = b[:n]
|
||||
|
||||
sum := 0
|
||||
for _, size := range c.sizes {
|
||||
@@ -133,24 +132,24 @@ func (c *headerManagerConn) ReadFrom(p []byte) (n int, addr net.Addr, err error)
|
||||
}
|
||||
|
||||
if n < sum {
|
||||
errors.LogError(context.Background(), "[mask] drop packet from ", addr, " with size ", n)
|
||||
errors.LogError(context.Background(), "[mask] drop packet from ", addr, " with size ", len(b))
|
||||
continue
|
||||
}
|
||||
|
||||
for i := range c.conns {
|
||||
n, _, err = c.conns[i].ReadFrom(buf)
|
||||
n, _, err = c.conns[i].ReadFrom(b)
|
||||
if err != nil {
|
||||
errors.LogErrorInner(context.Background(), err, "[mask] drop packet from ", addr, " with size ", n)
|
||||
errors.LogErrorInner(context.Background(), err, "[mask] drop packet from ", addr, " with size ", len(b))
|
||||
break
|
||||
}
|
||||
buf = buf[c.sizes[i] : n+c.sizes[i]]
|
||||
b = b[c.sizes[i] : n+c.sizes[i]]
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
return copy(p, buf), addr, nil
|
||||
return copy(p, b), addr, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,7 +212,7 @@ func NewTcpmaskManager(tcpmasks []Tcpmask) *TcpmaskManager {
|
||||
|
||||
func (m *TcpmaskManager) WrapConnClient(raw net.Conn) (net.Conn, error) {
|
||||
var err error
|
||||
for _, mask := range slices.Backward(m.tcpmasks) {
|
||||
for _, mask := range m.tcpmasks {
|
||||
raw, err = mask.WrapConnClient(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -224,7 +223,7 @@ func (m *TcpmaskManager) WrapConnClient(raw net.Conn) (net.Conn, error) {
|
||||
|
||||
func (m *TcpmaskManager) WrapConnServer(raw net.Conn) (net.Conn, error) {
|
||||
var err error
|
||||
for _, mask := range slices.Backward(m.tcpmasks) {
|
||||
for _, mask := range m.tcpmasks {
|
||||
raw, err = mask.WrapConnServer(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -25,12 +25,12 @@ type Config struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
PacketsFrom int64 `protobuf:"varint,1,opt,name=packets_from,json=packetsFrom,proto3" json:"packets_from,omitempty"`
|
||||
PacketsTo int64 `protobuf:"varint,2,opt,name=packets_to,json=packetsTo,proto3" json:"packets_to,omitempty"`
|
||||
LengthMin int64 `protobuf:"varint,3,opt,name=length_min,json=lengthMin,proto3" json:"length_min,omitempty"`
|
||||
LengthMax int64 `protobuf:"varint,4,opt,name=length_max,json=lengthMax,proto3" json:"length_max,omitempty"`
|
||||
DelayMin int64 `protobuf:"varint,5,opt,name=delay_min,json=delayMin,proto3" json:"delay_min,omitempty"`
|
||||
DelayMax int64 `protobuf:"varint,6,opt,name=delay_max,json=delayMax,proto3" json:"delay_max,omitempty"`
|
||||
MaxSplitMin int64 `protobuf:"varint,7,opt,name=max_split_min,json=maxSplitMin,proto3" json:"max_split_min,omitempty"`
|
||||
MaxSplitMax int64 `protobuf:"varint,8,opt,name=max_split_max,json=maxSplitMax,proto3" json:"max_split_max,omitempty"`
|
||||
LengthsMin []int64 `protobuf:"varint,9,rep,packed,name=lengths_min,json=lengthsMin,proto3" json:"lengths_min,omitempty"`
|
||||
LengthsMax []int64 `protobuf:"varint,10,rep,packed,name=lengths_max,json=lengthsMax,proto3" json:"lengths_max,omitempty"`
|
||||
DelaysMin []int64 `protobuf:"varint,11,rep,packed,name=delays_min,json=delaysMin,proto3" json:"delays_min,omitempty"`
|
||||
DelaysMax []int64 `protobuf:"varint,12,rep,packed,name=delays_max,json=delaysMax,proto3" json:"delays_max,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@@ -79,6 +79,34 @@ func (x *Config) GetPacketsTo() int64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Config) GetLengthMin() int64 {
|
||||
if x != nil {
|
||||
return x.LengthMin
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Config) GetLengthMax() int64 {
|
||||
if x != nil {
|
||||
return x.LengthMax
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Config) GetDelayMin() int64 {
|
||||
if x != nil {
|
||||
return x.DelayMin
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Config) GetDelayMax() int64 {
|
||||
if x != nil {
|
||||
return x.DelayMax
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Config) GetMaxSplitMin() int64 {
|
||||
if x != nil {
|
||||
return x.MaxSplitMin
|
||||
@@ -93,54 +121,23 @@ func (x *Config) GetMaxSplitMax() int64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Config) GetLengthsMin() []int64 {
|
||||
if x != nil {
|
||||
return x.LengthsMin
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Config) GetLengthsMax() []int64 {
|
||||
if x != nil {
|
||||
return x.LengthsMax
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Config) GetDelaysMin() []int64 {
|
||||
if x != nil {
|
||||
return x.DelaysMin
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Config) GetDelaysMax() []int64 {
|
||||
if x != nil {
|
||||
return x.DelaysMax
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_transport_internet_finalmask_fragment_config_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_transport_internet_finalmask_fragment_config_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"2transport/internet/finalmask/fragment/config.proto\x12*xray.transport.internet.finalmask.fragment\"\x92\x02\n" +
|
||||
"2transport/internet/finalmask/fragment/config.proto\x12*xray.transport.internet.finalmask.fragment\"\x8a\x02\n" +
|
||||
"\x06Config\x12!\n" +
|
||||
"\fpackets_from\x18\x01 \x01(\x03R\vpacketsFrom\x12\x1d\n" +
|
||||
"\n" +
|
||||
"packets_to\x18\x02 \x01(\x03R\tpacketsTo\x12\"\n" +
|
||||
"packets_to\x18\x02 \x01(\x03R\tpacketsTo\x12\x1d\n" +
|
||||
"\n" +
|
||||
"length_min\x18\x03 \x01(\x03R\tlengthMin\x12\x1d\n" +
|
||||
"\n" +
|
||||
"length_max\x18\x04 \x01(\x03R\tlengthMax\x12\x1b\n" +
|
||||
"\tdelay_min\x18\x05 \x01(\x03R\bdelayMin\x12\x1b\n" +
|
||||
"\tdelay_max\x18\x06 \x01(\x03R\bdelayMax\x12\"\n" +
|
||||
"\rmax_split_min\x18\a \x01(\x03R\vmaxSplitMin\x12\"\n" +
|
||||
"\rmax_split_max\x18\b \x01(\x03R\vmaxSplitMax\x12\x1f\n" +
|
||||
"\vlengths_min\x18\t \x03(\x03R\n" +
|
||||
"lengthsMin\x12\x1f\n" +
|
||||
"\vlengths_max\x18\n" +
|
||||
" \x03(\x03R\n" +
|
||||
"lengthsMax\x12\x1d\n" +
|
||||
"\n" +
|
||||
"delays_min\x18\v \x03(\x03R\tdelaysMin\x12\x1d\n" +
|
||||
"\n" +
|
||||
"delays_max\x18\f \x03(\x03R\tdelaysMaxB\xa0\x01\n" +
|
||||
"\rmax_split_max\x18\b \x01(\x03R\vmaxSplitMaxB\xa0\x01\n" +
|
||||
".com.xray.transport.internet.finalmask.fragmentP\x01Z?github.com/xtls/xray-core/transport/internet/finalmask/fragment\xaa\x02*Xray.Transport.Internet.Finalmask.Fragmentb\x06proto3"
|
||||
|
||||
var (
|
||||
|
||||
@@ -9,10 +9,10 @@ option java_multiple_files = true;
|
||||
message Config {
|
||||
int64 packets_from = 1;
|
||||
int64 packets_to = 2;
|
||||
int64 length_min = 3;
|
||||
int64 length_max = 4;
|
||||
int64 delay_min = 5;
|
||||
int64 delay_max = 6;
|
||||
int64 max_split_min = 7;
|
||||
int64 max_split_max = 8;
|
||||
repeated int64 lengths_min = 9;
|
||||
repeated int64 lengths_max = 10;
|
||||
repeated int64 delays_min = 11;
|
||||
repeated int64 delays_max = 12;
|
||||
}
|
||||
@@ -43,29 +43,6 @@ func (c *fragmentConn) Splice() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// lengthForSegment returns the length range (min, max) for the given segment index (0-based).
|
||||
// Clamps to the last entry when the index exceeds the list length.
|
||||
func (c *fragmentConn) lengthForSegment(segIdx int) (int64, int64) {
|
||||
if segIdx >= len(c.config.LengthsMin) {
|
||||
segIdx = len(c.config.LengthsMin) - 1
|
||||
}
|
||||
return c.config.LengthsMin[segIdx], c.config.LengthsMax[segIdx]
|
||||
}
|
||||
|
||||
// delayForSegment returns the delay range (min, max) for the given segment index (0-based).
|
||||
// Clamps to the last entry when the index exceeds the list length.
|
||||
func (c *fragmentConn) delayForSegment(segIdx int) (int64, int64) {
|
||||
if segIdx >= len(c.config.DelaysMin) {
|
||||
segIdx = len(c.config.DelaysMin) - 1
|
||||
}
|
||||
return c.config.DelaysMin[segIdx], c.config.DelaysMax[segIdx]
|
||||
}
|
||||
|
||||
// mergeTlsHelloSegments returns true only when delays has exactly one zero entry.
|
||||
func (c *fragmentConn) mergeTlsHelloSegments() bool {
|
||||
return len(c.config.DelaysMax) == 1 && c.config.DelaysMax[0] == 0
|
||||
}
|
||||
|
||||
func (c *fragmentConn) Write(p []byte) (n int, err error) {
|
||||
c.count++
|
||||
|
||||
@@ -80,13 +57,12 @@ func (c *fragmentConn) Write(p []byte) (n int, err error) {
|
||||
data := p[5:recordLen]
|
||||
buff := make([]byte, 2048)
|
||||
var hello []byte
|
||||
mergeHello := c.mergeTlsHelloSegments()
|
||||
maxSplit := crypto.RandBetween(c.config.MaxSplitMin, c.config.MaxSplitMax)
|
||||
var splitNum int64
|
||||
for from := 0; ; {
|
||||
lengthMin, lengthMax := c.lengthForSegment(int(splitNum))
|
||||
to := from + int(crypto.RandBetween(lengthMin, lengthMax))
|
||||
if to > len(data) || (maxSplit > 0 && splitNum+1 >= maxSplit) {
|
||||
to := from + int(crypto.RandBetween(c.config.LengthMin, c.config.LengthMax))
|
||||
splitNum++
|
||||
if to > len(data) || (maxSplit > 0 && splitNum >= maxSplit) {
|
||||
to = len(data)
|
||||
}
|
||||
l := to - from
|
||||
@@ -98,19 +74,15 @@ func (c *fragmentConn) Write(p []byte) (n int, err error) {
|
||||
from = to
|
||||
buff[3] = byte(l >> 8)
|
||||
buff[4] = byte(l)
|
||||
if mergeHello {
|
||||
if c.config.DelayMax == 0 {
|
||||
hello = append(hello, buff[:5+l]...)
|
||||
} else {
|
||||
delayMin, delayMax := c.delayForSegment(int(splitNum))
|
||||
_, err := c.Conn.Write(buff[:5+l])
|
||||
if delayMax > 0 {
|
||||
time.Sleep(time.Duration(crypto.RandBetween(delayMin, delayMax)) * time.Millisecond)
|
||||
}
|
||||
time.Sleep(time.Duration(crypto.RandBetween(c.config.DelayMin, c.config.DelayMax)) * time.Millisecond)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
splitNum++
|
||||
if from == len(data) {
|
||||
if len(hello) > 0 {
|
||||
_, err := c.Conn.Write(hello)
|
||||
@@ -135,9 +107,9 @@ func (c *fragmentConn) Write(p []byte) (n int, err error) {
|
||||
maxSplit := crypto.RandBetween(c.config.MaxSplitMin, c.config.MaxSplitMax)
|
||||
var splitNum int64
|
||||
for from := 0; ; {
|
||||
lengthMin, lengthMax := c.lengthForSegment(int(splitNum))
|
||||
to := from + int(crypto.RandBetween(lengthMin, lengthMax))
|
||||
if to > len(p) || (maxSplit > 0 && splitNum+1 >= maxSplit) {
|
||||
to := from + int(crypto.RandBetween(c.config.LengthMin, c.config.LengthMax))
|
||||
splitNum++
|
||||
if to > len(p) || (maxSplit > 0 && splitNum >= maxSplit) {
|
||||
to = len(p)
|
||||
}
|
||||
n, err := c.Conn.Write(p[from:to])
|
||||
@@ -145,11 +117,7 @@ func (c *fragmentConn) Write(p []byte) (n int, err error) {
|
||||
if err != nil {
|
||||
return from, err
|
||||
}
|
||||
delayMin, delayMax := c.delayForSegment(int(splitNum))
|
||||
if delayMax > 0 {
|
||||
time.Sleep(time.Duration(crypto.RandBetween(delayMin, delayMax)) * time.Millisecond)
|
||||
}
|
||||
splitNum++
|
||||
time.Sleep(time.Duration(crypto.RandBetween(c.config.DelayMin, c.config.DelayMax)) * time.Millisecond)
|
||||
if from >= len(p) {
|
||||
return from, nil
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package salamander
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"golang.org/x/crypto/blake2b"
|
||||
)
|
||||
|
||||
@@ -21,7 +21,8 @@ var ErrPSKTooShort = fmt.Errorf("PSK must be at least %d bytes", smPSKMinLen)
|
||||
// the BLAKE2b-256 hash of a pre-shared key combined with a random salt.
|
||||
// Packet format: [8-byte salt][payload]
|
||||
type SalamanderObfuscator struct {
|
||||
PSK []byte
|
||||
PSK []byte
|
||||
RandSrc *rand.Rand
|
||||
|
||||
lk sync.Mutex
|
||||
keyInput []byte
|
||||
@@ -36,6 +37,7 @@ func NewSalamanderObfuscator(psk []byte) (*SalamanderObfuscator, error) {
|
||||
copy(keyInput, pskCopy)
|
||||
return &SalamanderObfuscator{
|
||||
PSK: pskCopy,
|
||||
RandSrc: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||
keyInput: keyInput,
|
||||
}, nil
|
||||
}
|
||||
@@ -45,8 +47,8 @@ func (o *SalamanderObfuscator) Obfuscate(in, out []byte) int {
|
||||
if len(out) < outLen {
|
||||
return 0
|
||||
}
|
||||
common.Must2(rand.Read(out[:smSaltLen]))
|
||||
o.lk.Lock()
|
||||
_, _ = o.RandSrc.Read(out[:smSaltLen])
|
||||
key := o.keyLocked(out[:smSaltLen])
|
||||
o.lk.Unlock()
|
||||
for i, c := range in {
|
||||
|
||||
@@ -2,7 +2,7 @@ package sudoku
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand/v2"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
var perm4 = [24][4]byte{
|
||||
@@ -67,7 +67,7 @@ func pickPaddingChance(rng *rand.Rand, pMin, pMax int) int {
|
||||
if pMax == pMin {
|
||||
return pMin
|
||||
}
|
||||
return pMin + rng.IntN(pMax-pMin+1)
|
||||
return pMin + rng.Intn(pMax-pMin+1)
|
||||
}
|
||||
|
||||
func (c *codec) shouldPad() bool {
|
||||
@@ -77,7 +77,7 @@ func (c *codec) shouldPad() bool {
|
||||
if c.paddingChance >= 100 {
|
||||
return true
|
||||
}
|
||||
return c.rng.IntN(100) < c.paddingChance
|
||||
return c.rng.Intn(100) < c.paddingChance
|
||||
}
|
||||
|
||||
func (c *codec) currentTable() *table {
|
||||
@@ -89,7 +89,7 @@ func (c *codec) currentTable() *table {
|
||||
|
||||
func (c *codec) randomPadding(t *table) byte {
|
||||
pool := t.layout.paddingPool
|
||||
return pool[c.rng.IntN(len(pool))]
|
||||
return pool[c.rng.Intn(len(pool))]
|
||||
}
|
||||
|
||||
func (c *codec) encode(in []byte) ([]byte, error) {
|
||||
@@ -112,8 +112,8 @@ func (c *codec) encode(in []byte) ([]byte, error) {
|
||||
return nil, fmt.Errorf("sudoku encode table missing for byte %d", b)
|
||||
}
|
||||
|
||||
hints := enc[c.rng.IntN(len(enc))]
|
||||
perm := perm4[c.rng.IntN(len(perm4))]
|
||||
hints := enc[c.rng.Intn(len(enc))]
|
||||
perm := perm4[c.rng.Intn(len(perm4))]
|
||||
for _, idx := range perm {
|
||||
if c.shouldPad() {
|
||||
out = append(out, c.randomPadding(t))
|
||||
|
||||
@@ -72,7 +72,7 @@ func (e *packedEncoder) maybePad(out []byte, layout *byteLayout) []byte {
|
||||
return append(out, layout.paddingPool[0])
|
||||
}
|
||||
for {
|
||||
b := layout.paddingPool[e.codec.rng.IntN(len(layout.paddingPool))]
|
||||
b := layout.paddingPool[e.codec.rng.Intn(len(layout.paddingPool))]
|
||||
if b != layout.padMarker {
|
||||
return append(out, b)
|
||||
}
|
||||
|
||||
@@ -7,12 +7,10 @@ import (
|
||||
"fmt"
|
||||
"math/bits"
|
||||
"math/rand"
|
||||
rand_v2 "math/rand/v2"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"time"
|
||||
)
|
||||
|
||||
type table struct {
|
||||
@@ -572,8 +570,11 @@ func sort4(in [4]byte) [4]byte {
|
||||
return in
|
||||
}
|
||||
|
||||
func newSeededRand() *rand_v2.Rand {
|
||||
var seedBytes [32]byte
|
||||
common.Must2(crypto_rand.Read(seedBytes[:]))
|
||||
return rand_v2.New(rand_v2.NewChaCha8(seedBytes))
|
||||
func newSeededRand() *rand.Rand {
|
||||
seed := time.Now().UnixNano()
|
||||
var seedBytes [8]byte
|
||||
if _, err := crypto_rand.Read(seedBytes[:]); err == nil {
|
||||
seed = int64(binary.BigEndian.Uint64(seedBytes[:]))
|
||||
}
|
||||
return rand.New(rand.NewSource(seed))
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package finalmask_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
@@ -9,6 +10,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
singM "github.com/sagernet/sing/common/metadata"
|
||||
singN "github.com/sagernet/sing/common/network"
|
||||
"github.com/xtls/xray-core/proxy"
|
||||
"github.com/xtls/xray-core/transport/internet/finalmask"
|
||||
"github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
|
||||
@@ -131,6 +134,24 @@ func (c *scriptedPacketConn) SetWriteDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type captureUDPHandler struct {
|
||||
gotMetadata chan singM.Metadata
|
||||
}
|
||||
|
||||
func (h *captureUDPHandler) NewConnection(_ context.Context, _ net.Conn, _ singM.Metadata) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *captureUDPHandler) NewPacketConnection(_ context.Context, _ singN.PacketConn, metadata singM.Metadata) error {
|
||||
select {
|
||||
case h.gotMetadata <- metadata:
|
||||
default:
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *captureUDPHandler) NewError(_ context.Context, _ error) {}
|
||||
|
||||
func newStandaloneEchoUDPConfig() *custom.UDPStandaloneConfig {
|
||||
return &custom.UDPStandaloneConfig{
|
||||
Client: []*custom.UDPItem{
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//go:build !linux
|
||||
|
||||
package xicmp
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,364 +0,0 @@
|
||||
//go:build linux
|
||||
|
||||
package xicmp
|
||||
|
||||
import (
|
||||
"context"
|
||||
goerrors "errors"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/transport/internet/finalmask"
|
||||
"golang.org/x/net/icmp"
|
||||
"golang.org/x/net/ipv4"
|
||||
"golang.org/x/net/ipv6"
|
||||
)
|
||||
|
||||
func clientIDToAddr(clientID [8]byte) *net.UDPAddr {
|
||||
ip := make(net.IP, 16)
|
||||
|
||||
ip[0] = 0xfd
|
||||
ip[1] = 0x00
|
||||
|
||||
copy(ip[8:], clientID[:])
|
||||
|
||||
return &net.UDPAddr{IP: ip}
|
||||
}
|
||||
|
||||
type record struct {
|
||||
id int
|
||||
seq int
|
||||
addr net.Addr
|
||||
dst net.IP
|
||||
last time.Time
|
||||
}
|
||||
|
||||
type xicmpConnServer struct {
|
||||
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, raw net.PacketConn) (net.PacketConn, error) {
|
||||
icmp4, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
icmp6, err := icmp.ListenPacket("ip6:ipv6-icmp", "::")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ips := make(map[netip.Addr]struct{})
|
||||
for _, ip := range c.IPs {
|
||||
ips[netip.MustParseAddr(ip)] = struct{}{}
|
||||
}
|
||||
|
||||
conn := &xicmpConnServer{
|
||||
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()
|
||||
go conn.recv4()
|
||||
go conn.recv6()
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (c *xicmpConnServer) closed() bool {
|
||||
select {
|
||||
case <-c.closedCh:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (c *xicmpConnServer) clean() {
|
||||
ticker := time.NewTicker(time.Minute / 2)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
now := time.Now()
|
||||
c.mu.Lock()
|
||||
for key, r := range c.rec {
|
||||
if now.Sub(r.last) > time.Minute {
|
||||
delete(c.rec, key)
|
||||
}
|
||||
}
|
||||
c.mu.Unlock()
|
||||
case <-c.closedCh:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *xicmpConnServer) recv4() {
|
||||
var b [finalmask.UDPSize]byte
|
||||
|
||||
for {
|
||||
if c.closed() {
|
||||
return
|
||||
}
|
||||
|
||||
n, cm, addr, err := c.ipv4PC.ReadFrom(b[:])
|
||||
if err != nil {
|
||||
var netErr net.Error
|
||||
if goerrors.As(err, &netErr) && netErr.Timeout() {
|
||||
select {
|
||||
case c.readCh <- packet{
|
||||
err: err,
|
||||
}:
|
||||
case <-c.closedCh:
|
||||
return
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
msg, err := icmp.ParseMessage(1, b[:n])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if msg.Type != ipv4.ICMPTypeEcho {
|
||||
continue
|
||||
}
|
||||
|
||||
echo, ok := msg.Body.(*icmp.Echo)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(echo.Data) <= 8 {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(c.ips) > 0 {
|
||||
netipAddr, ok := netip.AddrFromSlice(addr.(*net.IPAddr).IP)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := c.ips[netipAddr]; !ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
cAddr := clientIDToAddr([8]byte(echo.Data[:8]))
|
||||
|
||||
c.mu.Lock()
|
||||
c.rec[cAddr.String()] = record{
|
||||
id: echo.ID,
|
||||
seq: echo.Seq,
|
||||
addr: addr,
|
||||
dst: cm.Dst,
|
||||
last: time.Now(),
|
||||
}
|
||||
c.mu.Unlock()
|
||||
|
||||
p := pool.Get().([]byte)[:len(echo.Data[8:])]
|
||||
copy(p, echo.Data[8:])
|
||||
|
||||
select {
|
||||
case c.readCh <- packet{
|
||||
p: p,
|
||||
addr: cAddr,
|
||||
}:
|
||||
case <-c.closedCh:
|
||||
pool.Put(p)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *xicmpConnServer) recv6() {
|
||||
var b [finalmask.UDPSize]byte
|
||||
|
||||
for {
|
||||
if c.closed() {
|
||||
return
|
||||
}
|
||||
|
||||
n, cm, addr, err := c.ipv6PC.ReadFrom(b[:])
|
||||
if err != nil {
|
||||
var netErr net.Error
|
||||
if goerrors.As(err, &netErr) && netErr.Timeout() {
|
||||
select {
|
||||
case c.readCh <- packet{
|
||||
err: err,
|
||||
}:
|
||||
case <-c.closedCh:
|
||||
return
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
msg, err := icmp.ParseMessage(58, b[:n])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if msg.Type != ipv6.ICMPTypeEchoRequest {
|
||||
continue
|
||||
}
|
||||
|
||||
echo, ok := msg.Body.(*icmp.Echo)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(echo.Data) <= 8 {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(c.ips) > 0 {
|
||||
netipAddr, ok := netip.AddrFromSlice(addr.(*net.IPAddr).IP)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := c.ips[netipAddr]; !ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
cAddr := clientIDToAddr([8]byte(echo.Data[:8]))
|
||||
|
||||
c.mu.Lock()
|
||||
c.rec[cAddr.String()] = record{
|
||||
id: echo.ID,
|
||||
seq: echo.Seq,
|
||||
addr: addr,
|
||||
dst: cm.Dst,
|
||||
last: time.Now(),
|
||||
}
|
||||
c.mu.Unlock()
|
||||
|
||||
p := pool.Get().([]byte)[:len(echo.Data[8:])]
|
||||
copy(p, echo.Data[8:])
|
||||
|
||||
select {
|
||||
case c.readCh <- packet{
|
||||
p: p,
|
||||
addr: cAddr,
|
||||
}:
|
||||
case <-c.closedCh:
|
||||
pool.Put(p)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *xicmpConnServer) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
func (c *xicmpConnServer) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
if len(p)+8 > finalmask.UDPSize {
|
||||
errors.LogError(context.Background(), "drop packet to ", addr, " with size ", len(p))
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
r, ok := c.rec[addr.String()]
|
||||
if !ok {
|
||||
errors.LogError(context.Background(), "drop packet to ", addr, " with size ", len(p))
|
||||
c.mu.Unlock()
|
||||
return 0, nil
|
||||
}
|
||||
r.last = time.Now()
|
||||
c.rec[addr.String()] = r
|
||||
c.mu.Unlock()
|
||||
|
||||
// errors.LogDebug(context.Background(), "id ", r.id, " seq ", r.seq, " addr ", r.addr)
|
||||
|
||||
b := pool.Get().([]byte)[:finalmask.UDPSize]
|
||||
defer pool.Put(b)
|
||||
|
||||
copy(b[8:], p)
|
||||
|
||||
if r.addr.(*net.IPAddr).IP.To4() != nil {
|
||||
b = marshal(b, ipv4.ICMPTypeEchoReply, r.id, r.seq, len(p))
|
||||
_, err = c.ipv4PC.WriteTo(b, &ipv4.ControlMessage{Src: r.dst}, r.addr)
|
||||
} else {
|
||||
b = marshal(b, ipv6.ICMPTypeEchoReply, r.id, r.seq, len(p))
|
||||
_, err = c.ipv6PC.WriteTo(b, &ipv6.ControlMessage{Src: r.dst}, r.addr)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
errors.LogErrorInner(context.Background(), err, "xicmp write")
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (c *xicmpConnServer) Close() error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if c.closed() {
|
||||
return nil
|
||||
}
|
||||
close(c.closedCh)
|
||||
_ = c.icmp4.Close()
|
||||
_ = c.icmp6.Close()
|
||||
_ = c.conn.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *xicmpConnServer) LocalAddr() net.Addr {
|
||||
return c.conn.LocalAddr()
|
||||
}
|
||||
|
||||
func (c *xicmpConnServer) SetDeadline(t time.Time) error {
|
||||
_ = c.icmp4.SetDeadline(t)
|
||||
_ = c.icmp6.SetDeadline(t)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *xicmpConnServer) SetReadDeadline(t time.Time) error {
|
||||
_ = c.icmp4.SetReadDeadline(t)
|
||||
_ = c.icmp6.SetReadDeadline(t)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *xicmpConnServer) SetWriteDeadline(t time.Time) error {
|
||||
_ = c.icmp4.SetWriteDeadline(t)
|
||||
_ = c.icmp6.SetWriteDeadline(t)
|
||||
return nil
|
||||
}
|
||||
@@ -62,7 +62,7 @@ func dialgRPC(ctx context.Context, dest net.Destination, streamSettings *interne
|
||||
if err != nil {
|
||||
return nil, errors.New("Cannot dial gRPC").Base(err)
|
||||
}
|
||||
return encoding.NewMultiHunkConn(grpcService, nil, nil), nil
|
||||
return encoding.NewMultiHunkConn(grpcService, nil), nil
|
||||
}
|
||||
|
||||
errors.LogDebug(ctx, "using gRPC tun mode service name: `"+grpcSettings.getServiceName()+"` stream name: `"+grpcSettings.getTunStreamName()+"`")
|
||||
@@ -71,7 +71,7 @@ func dialgRPC(ctx context.Context, dest net.Destination, streamSettings *interne
|
||||
return nil, errors.New("Cannot dial gRPC").Base(err)
|
||||
}
|
||||
|
||||
return encoding.NewHunkConn(grpcService, nil, nil), nil
|
||||
return encoding.NewHunkConn(grpcService, nil), nil
|
||||
}
|
||||
|
||||
func getGrpcClient(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (*grpc.ClientConn, error) {
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/net/cnc"
|
||||
"github.com/xtls/xray-core/common/signal/done"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/peer"
|
||||
)
|
||||
|
||||
type HunkConn interface {
|
||||
@@ -36,8 +38,31 @@ func NewHunkReadWriter(hc HunkConn, cancel context.CancelFunc) *HunkReaderWriter
|
||||
return &HunkReaderWriter{hc, cancel, done.New(), nil, 0}
|
||||
}
|
||||
|
||||
func NewHunkConn(hc HunkConn, cancel context.CancelFunc, trustedXForwardedFor []string) net.Conn {
|
||||
rAddr := remoteAddrFromContext(hc.Context(), trustedXForwardedFor)
|
||||
func NewHunkConn(hc HunkConn, cancel context.CancelFunc) net.Conn {
|
||||
var rAddr net.Addr
|
||||
pr, ok := peer.FromContext(hc.Context())
|
||||
if ok {
|
||||
rAddr = pr.Addr
|
||||
} else {
|
||||
rAddr = &net.TCPAddr{
|
||||
IP: []byte{0, 0, 0, 0},
|
||||
Port: 0,
|
||||
}
|
||||
}
|
||||
|
||||
md, ok := metadata.FromIncomingContext(hc.Context())
|
||||
if ok {
|
||||
header := md.Get("x-real-ip")
|
||||
if len(header) > 0 {
|
||||
realip := net.ParseAddress(header[0])
|
||||
if realip.Family().IsIP() {
|
||||
rAddr = &net.TCPAddr{
|
||||
IP: realip.IP(),
|
||||
Port: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
wrc := NewHunkReadWriter(hc, cancel)
|
||||
return cnc.NewConnection(
|
||||
cnc.ConnectionInput(wrc),
|
||||
|
||||
@@ -3,12 +3,15 @@ package encoding
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
xnet "github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/net/cnc"
|
||||
"github.com/xtls/xray-core/common/signal/done"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/peer"
|
||||
)
|
||||
|
||||
type MultiHunkConn interface {
|
||||
@@ -31,8 +34,31 @@ func NewMultiHunkReadWriter(hc MultiHunkConn, cancel context.CancelFunc) *MultiH
|
||||
return &MultiHunkReaderWriter{hc, cancel, done.New(), nil}
|
||||
}
|
||||
|
||||
func NewMultiHunkConn(hc MultiHunkConn, cancel context.CancelFunc, trustedXForwardedFor []string) net.Conn {
|
||||
rAddr := remoteAddrFromContext(hc.Context(), trustedXForwardedFor)
|
||||
func NewMultiHunkConn(hc MultiHunkConn, cancel context.CancelFunc) net.Conn {
|
||||
var rAddr net.Addr
|
||||
pr, ok := peer.FromContext(hc.Context())
|
||||
if ok {
|
||||
rAddr = pr.Addr
|
||||
} else {
|
||||
rAddr = &net.TCPAddr{
|
||||
IP: []byte{0, 0, 0, 0},
|
||||
Port: 0,
|
||||
}
|
||||
}
|
||||
|
||||
md, ok := metadata.FromIncomingContext(hc.Context())
|
||||
if ok {
|
||||
header := md.Get("x-real-ip")
|
||||
if len(header) > 0 {
|
||||
realip := xnet.ParseAddress(header[0])
|
||||
if realip.Family().IsIP() {
|
||||
rAddr = &net.TCPAddr{
|
||||
IP: realip.IP(),
|
||||
Port: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
wrc := NewMultiHunkReadWriter(hc, cancel)
|
||||
return cnc.NewConnection(
|
||||
cnc.ConnectionInputMulti(wrc),
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
package encoding
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/peer"
|
||||
)
|
||||
|
||||
func remoteAddrFromContext(ctx context.Context, trusted []string) net.Addr {
|
||||
var remoteAddr net.Addr
|
||||
if pr, ok := peer.FromContext(ctx); ok {
|
||||
remoteAddr = pr.Addr
|
||||
} else {
|
||||
remoteAddr = &net.TCPAddr{
|
||||
IP: []byte{0, 0, 0, 0},
|
||||
Port: 0,
|
||||
}
|
||||
}
|
||||
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
return remoteAddr
|
||||
}
|
||||
|
||||
if forwardedAddr := parseTrustedXForwardedFor(md, trusted, remoteAddr); forwardedAddr != nil && forwardedAddr.Family().IsIP() {
|
||||
remoteAddr = &net.TCPAddr{
|
||||
IP: forwardedAddr.IP(),
|
||||
Port: 0,
|
||||
}
|
||||
}
|
||||
return remoteAddr
|
||||
}
|
||||
|
||||
func parseTrustedXForwardedFor(md metadata.MD, trusted []string, remoteAddr net.Addr) net.Address {
|
||||
values := md.Get("X-Forwarded-For")
|
||||
if len(values) == 0 || values[0] == "" {
|
||||
return nil
|
||||
}
|
||||
value := values[0]
|
||||
for _, t := range trusted {
|
||||
if len(md.Get(t)) > 0 {
|
||||
if idx := strings.IndexByte(value, ','); idx >= 0 {
|
||||
value = value[:idx]
|
||||
}
|
||||
return net.ParseAddress(value)
|
||||
}
|
||||
}
|
||||
if len(trusted) == 0 {
|
||||
errors.LogWarning(context.Background(), `received "X-Forwarded-For" from `, remoteAddr, ` but "sockopt.trustedXForwardedFor" is not configured; ignoring it and using the real remote address`)
|
||||
} else {
|
||||
errors.LogError(context.Background(), `ignored potentially forged "X-Forwarded-For" from `, remoteAddr, `: `, value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package encoding
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/peer"
|
||||
)
|
||||
|
||||
func TestRemoteAddrFromContext(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
metadata metadata.MD
|
||||
trustedXForwardedFor []string
|
||||
expectedRemoteAddress string
|
||||
}{
|
||||
{
|
||||
name: "trust X-Forwarded-For when configured",
|
||||
metadata: metadata.Pairs("X-Forwarded-For", "2.2.2.2, 3.3.3.3"),
|
||||
trustedXForwardedFor: []string{"X-Forwarded-For"},
|
||||
expectedRemoteAddress: "2.2.2.2:0",
|
||||
},
|
||||
{
|
||||
name: "trust X-Forwarded-For with trusted marker",
|
||||
metadata: metadata.Pairs("X-Forwarded-For", "4.4.4.4", "X-Trusted-CDN", "1"),
|
||||
trustedXForwardedFor: []string{"X-Trusted-CDN"},
|
||||
expectedRemoteAddress: "4.4.4.4:0",
|
||||
},
|
||||
{
|
||||
name: "ignore X-Forwarded-For without trusted marker",
|
||||
metadata: metadata.Pairs("X-Forwarded-For", "5.5.5.5"),
|
||||
trustedXForwardedFor: []string{"X-Trusted-CDN"},
|
||||
expectedRemoteAddress: "127.0.0.1:12345",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ctx := peer.NewContext(metadata.NewIncomingContext(context.Background(), test.metadata), &peer.Peer{
|
||||
Addr: &net.TCPAddr{
|
||||
IP: net.ParseIP("127.0.0.1"),
|
||||
Port: 12345,
|
||||
},
|
||||
})
|
||||
remoteAddr := remoteAddrFromContext(ctx, test.trustedXForwardedFor)
|
||||
if remoteAddr.String() != test.expectedRemoteAddress {
|
||||
t.Fatalf("unexpected remote address: %s", remoteAddr.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -19,25 +19,24 @@ import (
|
||||
|
||||
type Listener struct {
|
||||
encoding.UnimplementedGRPCServiceServer
|
||||
ctx context.Context
|
||||
handler internet.ConnHandler
|
||||
local net.Addr
|
||||
config *Config
|
||||
trustedXForwardedFor []string
|
||||
ctx context.Context
|
||||
handler internet.ConnHandler
|
||||
local net.Addr
|
||||
config *Config
|
||||
|
||||
s *grpc.Server
|
||||
}
|
||||
|
||||
func (l Listener) Tun(server encoding.GRPCService_TunServer) error {
|
||||
tunCtx, cancel := context.WithCancel(l.ctx)
|
||||
l.handler(encoding.NewHunkConn(server, cancel, l.trustedXForwardedFor))
|
||||
l.handler(encoding.NewHunkConn(server, cancel))
|
||||
<-tunCtx.Done()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l Listener) TunMulti(server encoding.GRPCService_TunMultiServer) error {
|
||||
tunCtx, cancel := context.WithCancel(l.ctx)
|
||||
l.handler(encoding.NewMultiHunkConn(server, cancel, l.trustedXForwardedFor))
|
||||
l.handler(encoding.NewMultiHunkConn(server, cancel))
|
||||
<-tunCtx.Done()
|
||||
return nil
|
||||
}
|
||||
@@ -75,9 +74,6 @@ func Listen(ctx context.Context, address net.Address, port net.Port, settings *i
|
||||
}
|
||||
|
||||
listener.ctx = ctx
|
||||
if settings.SocketSettings != nil {
|
||||
listener.trustedXForwardedFor = settings.SocketSettings.TrustedXForwardedFor
|
||||
}
|
||||
|
||||
config := tls.ConfigFromStreamSettings(settings)
|
||||
|
||||
|
||||
@@ -138,9 +138,6 @@ func TestDialWithRemoteAddr(t *testing.T) {
|
||||
ProtocolSettings: &Config{
|
||||
Path: "httpupgrade",
|
||||
},
|
||||
SocketSettings: &internet.SocketConfig{
|
||||
TrustedXForwardedFor: []string{"X-Forwarded-For"},
|
||||
},
|
||||
}, func(conn stat.Connection) {
|
||||
go func(c stat.Connection) {
|
||||
defer c.Close()
|
||||
|
||||
@@ -80,12 +80,24 @@ func (s *server) upgrade(conn net.Conn) (stat.Connection, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
remoteAddr := conn.RemoteAddr()
|
||||
var trustedXFF []string
|
||||
if s.socketSettings != nil {
|
||||
trustedXFF = s.socketSettings.TrustedXForwardedFor
|
||||
var forwardedAddrs []net.Address
|
||||
if s.socketSettings != nil && len(s.socketSettings.TrustedXForwardedFor) > 0 {
|
||||
for _, key := range s.socketSettings.TrustedXForwardedFor {
|
||||
if len(req.Header.Values(key)) > 0 {
|
||||
forwardedAddrs = http_proto.ParseXForwardedFor(req.Header)
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
forwardedAddrs = http_proto.ParseXForwardedFor(req.Header)
|
||||
}
|
||||
remoteAddr := conn.RemoteAddr()
|
||||
if len(forwardedAddrs) > 0 && forwardedAddrs[0].Family().IsIP() {
|
||||
remoteAddr = &net.TCPAddr{
|
||||
IP: forwardedAddrs[0].IP(),
|
||||
Port: int(0),
|
||||
}
|
||||
}
|
||||
remoteAddr = http_proto.ApplyTrustedXForwardedFor(req.Header, trustedXFF, remoteAddr)
|
||||
|
||||
return stat.Connection(newConnection(conn, remoteAddr)), nil
|
||||
}
|
||||
|
||||
@@ -126,8 +126,6 @@ func (c *client) dial(ctx context.Context) error {
|
||||
switch c := conn.(type) {
|
||||
case *internet.PacketConnWrapper:
|
||||
pktConn = c.PacketConn
|
||||
case *cnc.Connection:
|
||||
pktConn = &internet.FakePacketConn{Conn: c}
|
||||
default:
|
||||
panic(reflect.TypeOf(c))
|
||||
}
|
||||
@@ -137,30 +135,36 @@ func (c *client) dial(ctx context.Context) error {
|
||||
|
||||
var pktConn net.PacketConn
|
||||
var udpAddr *net.UDPAddr
|
||||
var index int
|
||||
|
||||
if len(quicParams.UdpHop.Ports) > 0 {
|
||||
index = rand.Intn(len(quicParams.UdpHop.Ports))
|
||||
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 {
|
||||
conn, err := internet.DialSystem(ctx, c.dest, c.socketConfig)
|
||||
if err != nil {
|
||||
return errors.New("failed to dial to dest").Base(err)
|
||||
}
|
||||
switch c := conn.(type) {
|
||||
case *internet.PacketConnWrapper:
|
||||
pktConn = c.PacketConn
|
||||
udpAddr = conn.RemoteAddr().(*net.UDPAddr)
|
||||
default:
|
||||
panic(reflect.TypeOf(c))
|
||||
}
|
||||
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)
|
||||
} else {
|
||||
conn, err := internet.DialSystem(ctx, c.dest, c.socketConfig)
|
||||
if err != nil {
|
||||
return errors.New("failed to dial to dest").Base(err)
|
||||
}
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
if c.udpmaskManager != nil {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"math/rand"
|
||||
"net"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/transport/internet/finalmask"
|
||||
@@ -137,8 +138,8 @@ func (u *UdpHopPacketConn) hop() {
|
||||
if u.closed {
|
||||
return
|
||||
}
|
||||
addrIndex := rand.Intn(len(u.Addrs))
|
||||
newConn, err := u.ListenUDPFunc(u.Addrs[addrIndex].(*net.UDPAddr))
|
||||
u.addrIndex = rand.Intn(len(u.Addrs))
|
||||
newConn, err := u.ListenUDPFunc(u.Addrs[u.addrIndex].(*net.UDPAddr))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -146,7 +147,6 @@ func (u *UdpHopPacketConn) hop() {
|
||||
_ = u.prevConn.Close()
|
||||
}
|
||||
u.prevConn = u.currentConn
|
||||
u.addrIndex = addrIndex
|
||||
u.currentConn = newConn
|
||||
if !u.deadline.IsZero() {
|
||||
_ = u.currentConn.SetDeadline(u.deadline)
|
||||
@@ -241,6 +241,16 @@ func (u *UdpHopPacketConn) SetWriteDeadline(t time.Time) error {
|
||||
return u.currentConn.SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
func (u *UdpHopPacketConn) SyscallConn() (syscall.RawConn, error) {
|
||||
u.connMutex.RLock()
|
||||
defer u.connMutex.RUnlock()
|
||||
sc, ok := u.currentConn.(syscall.Conn)
|
||||
if !ok {
|
||||
return nil, errors.New("not supported")
|
||||
}
|
||||
return sc.SyscallConn()
|
||||
}
|
||||
|
||||
func ToAddrs(ip net.IP, ports []uint32) []net.Addr {
|
||||
var addrs []net.Addr
|
||||
for _, port := range ports {
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"net/http/httptrace"
|
||||
"sync"
|
||||
|
||||
"github.com/apernet/quic-go/http3"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
@@ -60,11 +59,7 @@ func (c *DefaultDialerClient) OpenStream(ctx context.Context, url string, sessio
|
||||
if body != nil {
|
||||
method = c.transportConfig.GetNormalizedUplinkHTTPMethod() // stream-up/one
|
||||
}
|
||||
req, err := http.NewRequestWithContext(context.WithoutCancel(ctx), method, url, body)
|
||||
if err != nil {
|
||||
errors.LogInfoInner(ctx, err, "failed to create HTTP request for "+url)
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
req, _ := http.NewRequestWithContext(context.WithoutCancel(ctx), method, url, body)
|
||||
c.transportConfig.FillStreamRequest(req, sessionId, "")
|
||||
|
||||
wrc = &WaitReadCloser{Wait: make(chan struct{})}
|
||||
@@ -177,15 +172,6 @@ func (c *DefaultDialerClient) PostPacket(ctx context.Context, url string, sessio
|
||||
return nil
|
||||
}
|
||||
|
||||
// HTTP/1.1 and HTTP/2 will close itself, we only handle HTTP/3 here
|
||||
func (c *DefaultDialerClient) Close() error {
|
||||
transport := c.client.Transport
|
||||
if h3Transport, ok := transport.(*http3.Transport); ok {
|
||||
h3Transport.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type WaitReadCloser struct {
|
||||
Wait chan struct{}
|
||||
io.ReadCloser
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand/v2"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
@@ -12,7 +11,6 @@ import (
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
"github.com/xtls/xray-core/common/crypto"
|
||||
"github.com/xtls/xray-core/common/utils"
|
||||
"github.com/xtls/xray-core/common/uuid"
|
||||
"github.com/xtls/xray-core/transport/internet"
|
||||
)
|
||||
|
||||
@@ -133,26 +131,26 @@ func (c *Config) GetNormalizedUplinkHTTPMethod() string {
|
||||
return c.UplinkHTTPMethod
|
||||
}
|
||||
|
||||
func (c *Config) GetNormalizedScMaxEachPostBytes() *RangeConfig {
|
||||
func (c *Config) GetNormalizedScMaxEachPostBytes() RangeConfig {
|
||||
if c.ScMaxEachPostBytes == nil || c.ScMaxEachPostBytes.To == 0 {
|
||||
return &RangeConfig{
|
||||
return RangeConfig{
|
||||
From: 1000000,
|
||||
To: 1000000,
|
||||
}
|
||||
}
|
||||
|
||||
return c.ScMaxEachPostBytes
|
||||
return *c.ScMaxEachPostBytes
|
||||
}
|
||||
|
||||
func (c *Config) GetNormalizedScMinPostsIntervalMs() *RangeConfig {
|
||||
func (c *Config) GetNormalizedScMinPostsIntervalMs() RangeConfig {
|
||||
if c.ScMinPostsIntervalMs == nil || c.ScMinPostsIntervalMs.To == 0 {
|
||||
return &RangeConfig{
|
||||
return RangeConfig{
|
||||
From: 30,
|
||||
To: 30,
|
||||
}
|
||||
}
|
||||
|
||||
return c.ScMinPostsIntervalMs
|
||||
return *c.ScMinPostsIntervalMs
|
||||
}
|
||||
|
||||
func (c *Config) GetNormalizedScMaxBufferedPosts() int {
|
||||
@@ -163,27 +161,27 @@ func (c *Config) GetNormalizedScMaxBufferedPosts() int {
|
||||
return int(c.ScMaxBufferedPosts)
|
||||
}
|
||||
|
||||
func (c *Config) GetNormalizedScStreamUpServerSecs() *RangeConfig {
|
||||
func (c *Config) GetNormalizedScStreamUpServerSecs() RangeConfig {
|
||||
if c.ScStreamUpServerSecs == nil || c.ScStreamUpServerSecs.To == 0 {
|
||||
return &RangeConfig{
|
||||
return RangeConfig{
|
||||
From: 20,
|
||||
To: 80,
|
||||
}
|
||||
}
|
||||
|
||||
return c.ScStreamUpServerSecs
|
||||
return *c.ScStreamUpServerSecs
|
||||
}
|
||||
|
||||
func (c *Config) GetNormalizedUplinkChunkSize() *RangeConfig {
|
||||
func (c *Config) GetNormalizedUplinkChunkSize() RangeConfig {
|
||||
if c.UplinkChunkSize == nil || c.UplinkChunkSize.To == 0 {
|
||||
switch c.UplinkDataPlacement {
|
||||
case PlacementCookie:
|
||||
return &RangeConfig{
|
||||
return RangeConfig{
|
||||
From: 2 * 1024, // 2 KiB
|
||||
To: 3 * 1024, // 3 KiB
|
||||
}
|
||||
case PlacementHeader:
|
||||
return &RangeConfig{
|
||||
return RangeConfig{
|
||||
From: 3 * 1000, // 3 KB
|
||||
To: 4 * 1000, // 4 KB
|
||||
}
|
||||
@@ -191,13 +189,13 @@ func (c *Config) GetNormalizedUplinkChunkSize() *RangeConfig {
|
||||
return c.GetNormalizedScMaxEachPostBytes()
|
||||
}
|
||||
} else if c.UplinkChunkSize.From < 64 {
|
||||
return &RangeConfig{
|
||||
return RangeConfig{
|
||||
From: 64,
|
||||
To: max(64, c.UplinkChunkSize.To),
|
||||
}
|
||||
}
|
||||
|
||||
return c.UplinkChunkSize
|
||||
return *c.UplinkChunkSize
|
||||
}
|
||||
|
||||
func (c *Config) GetNormalizedServerMaxHeaderBytes() int {
|
||||
@@ -209,10 +207,10 @@ func (c *Config) GetNormalizedServerMaxHeaderBytes() int {
|
||||
}
|
||||
|
||||
func (c *Config) GetNormalizedSessionPlacement() string {
|
||||
if c.SessionIDPlacement == "" {
|
||||
if c.SessionPlacement == "" {
|
||||
return PlacementPath
|
||||
}
|
||||
return c.SessionIDPlacement
|
||||
return c.SessionPlacement
|
||||
}
|
||||
|
||||
func (c *Config) GetNormalizedSeqPlacement() string {
|
||||
@@ -230,8 +228,8 @@ func (c *Config) GetNormalizedUplinkDataPlacement() string {
|
||||
}
|
||||
|
||||
func (c *Config) GetNormalizedSessionKey() string {
|
||||
if c.SessionIDKey != "" {
|
||||
return c.SessionIDKey
|
||||
if c.SessionKey != "" {
|
||||
return c.SessionKey
|
||||
}
|
||||
switch c.GetNormalizedSessionPlacement() {
|
||||
case PlacementHeader:
|
||||
@@ -419,59 +417,59 @@ func (c *Config) ExtractMetaFromRequest(req *http.Request, path string) (session
|
||||
return sessionId, seqStr
|
||||
}
|
||||
|
||||
func (m *XmuxConfig) GetNormalizedMaxConcurrency() *RangeConfig {
|
||||
func (m *XmuxConfig) GetNormalizedMaxConcurrency() RangeConfig {
|
||||
if m.MaxConcurrency == nil {
|
||||
return &RangeConfig{
|
||||
return RangeConfig{
|
||||
From: 0,
|
||||
To: 0,
|
||||
}
|
||||
}
|
||||
|
||||
return m.MaxConcurrency
|
||||
return *m.MaxConcurrency
|
||||
}
|
||||
|
||||
func (m *XmuxConfig) GetNormalizedMaxConnections() *RangeConfig {
|
||||
func (m *XmuxConfig) GetNormalizedMaxConnections() RangeConfig {
|
||||
if m.MaxConnections == nil {
|
||||
return &RangeConfig{
|
||||
return RangeConfig{
|
||||
From: 0,
|
||||
To: 0,
|
||||
}
|
||||
}
|
||||
|
||||
return m.MaxConnections
|
||||
return *m.MaxConnections
|
||||
}
|
||||
|
||||
func (m *XmuxConfig) GetNormalizedCMaxReuseTimes() *RangeConfig {
|
||||
func (m *XmuxConfig) GetNormalizedCMaxReuseTimes() RangeConfig {
|
||||
if m.CMaxReuseTimes == nil {
|
||||
return &RangeConfig{
|
||||
return RangeConfig{
|
||||
From: 0,
|
||||
To: 0,
|
||||
}
|
||||
}
|
||||
|
||||
return m.CMaxReuseTimes
|
||||
return *m.CMaxReuseTimes
|
||||
}
|
||||
|
||||
func (m *XmuxConfig) GetNormalizedHMaxRequestTimes() *RangeConfig {
|
||||
func (m *XmuxConfig) GetNormalizedHMaxRequestTimes() RangeConfig {
|
||||
if m.HMaxRequestTimes == nil {
|
||||
return &RangeConfig{
|
||||
return RangeConfig{
|
||||
From: 0,
|
||||
To: 0,
|
||||
}
|
||||
}
|
||||
|
||||
return m.HMaxRequestTimes
|
||||
return *m.HMaxRequestTimes
|
||||
}
|
||||
|
||||
func (m *XmuxConfig) GetNormalizedHMaxReusableSecs() *RangeConfig {
|
||||
func (m *XmuxConfig) GetNormalizedHMaxReusableSecs() RangeConfig {
|
||||
if m.HMaxReusableSecs == nil {
|
||||
return &RangeConfig{
|
||||
return RangeConfig{
|
||||
From: 0,
|
||||
To: 0,
|
||||
}
|
||||
}
|
||||
|
||||
return m.HMaxReusableSecs
|
||||
return *m.HMaxReusableSecs
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -480,44 +478,10 @@ func init() {
|
||||
}))
|
||||
}
|
||||
|
||||
func (c *RangeConfig) rand() int32 {
|
||||
if c == nil {
|
||||
return 0
|
||||
}
|
||||
func (c RangeConfig) rand() int32 {
|
||||
return int32(crypto.RandBetween(int64(c.From), int64(c.To)))
|
||||
}
|
||||
|
||||
// predefined
|
||||
var PredefinedTable = map[string]string{
|
||||
"ALPHABET": "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
||||
"Alphabet": "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
|
||||
"BASE36": "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
||||
"Base62": "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
|
||||
"HEX": "0123456789ABCDEF",
|
||||
"alphabet": "abcdefghijklmnopqrstuvwxyz",
|
||||
"base36": "0123456789abcdefghijklmnopqrstuvwxyz",
|
||||
"hex": "0123456789abcdef",
|
||||
"number": "0123456789",
|
||||
}
|
||||
|
||||
func (c *Config) GenerateSessionID() string {
|
||||
length := c.SessionIDLength.rand()
|
||||
table := c.SessionIDTable
|
||||
if predefined, ok := PredefinedTable[table]; ok {
|
||||
table = predefined
|
||||
}
|
||||
if table != "" && length > 0 {
|
||||
id := make([]byte, length)
|
||||
for i := range id {
|
||||
id[i] = table[rand.N(len(table))]
|
||||
}
|
||||
return string(id)
|
||||
} else {
|
||||
uuid := uuid.New()
|
||||
return uuid.String()
|
||||
}
|
||||
}
|
||||
|
||||
func appendToPath(path, value string) string {
|
||||
if strings.HasSuffix(path, "/") {
|
||||
return path + value
|
||||
|
||||
@@ -179,16 +179,14 @@ type Config struct {
|
||||
XPaddingPlacement string `protobuf:"bytes,17,opt,name=xPaddingPlacement,proto3" json:"xPaddingPlacement,omitempty"`
|
||||
XPaddingMethod string `protobuf:"bytes,18,opt,name=xPaddingMethod,proto3" json:"xPaddingMethod,omitempty"`
|
||||
UplinkHTTPMethod string `protobuf:"bytes,19,opt,name=uplinkHTTPMethod,proto3" json:"uplinkHTTPMethod,omitempty"`
|
||||
SessionIDPlacement string `protobuf:"bytes,20,opt,name=sessionIDPlacement,proto3" json:"sessionIDPlacement,omitempty"`
|
||||
SessionIDKey string `protobuf:"bytes,21,opt,name=sessionIDKey,proto3" json:"sessionIDKey,omitempty"`
|
||||
SessionPlacement string `protobuf:"bytes,20,opt,name=sessionPlacement,proto3" json:"sessionPlacement,omitempty"`
|
||||
SessionKey string `protobuf:"bytes,21,opt,name=sessionKey,proto3" json:"sessionKey,omitempty"`
|
||||
SeqPlacement string `protobuf:"bytes,22,opt,name=seqPlacement,proto3" json:"seqPlacement,omitempty"`
|
||||
SeqKey string `protobuf:"bytes,23,opt,name=seqKey,proto3" json:"seqKey,omitempty"`
|
||||
UplinkDataPlacement string `protobuf:"bytes,24,opt,name=uplinkDataPlacement,proto3" json:"uplinkDataPlacement,omitempty"`
|
||||
UplinkDataKey string `protobuf:"bytes,25,opt,name=uplinkDataKey,proto3" json:"uplinkDataKey,omitempty"`
|
||||
UplinkChunkSize *RangeConfig `protobuf:"bytes,26,opt,name=uplinkChunkSize,proto3" json:"uplinkChunkSize,omitempty"`
|
||||
ServerMaxHeaderBytes int32 `protobuf:"varint,27,opt,name=serverMaxHeaderBytes,proto3" json:"serverMaxHeaderBytes,omitempty"`
|
||||
SessionIDTable string `protobuf:"bytes,28,opt,name=sessionIDTable,proto3" json:"sessionIDTable,omitempty"`
|
||||
SessionIDLength *RangeConfig `protobuf:"bytes,29,opt,name=sessionIDLength,proto3" json:"sessionIDLength,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@@ -356,16 +354,16 @@ func (x *Config) GetUplinkHTTPMethod() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Config) GetSessionIDPlacement() string {
|
||||
func (x *Config) GetSessionPlacement() string {
|
||||
if x != nil {
|
||||
return x.SessionIDPlacement
|
||||
return x.SessionPlacement
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Config) GetSessionIDKey() string {
|
||||
func (x *Config) GetSessionKey() string {
|
||||
if x != nil {
|
||||
return x.SessionIDKey
|
||||
return x.SessionKey
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -412,20 +410,6 @@ func (x *Config) GetServerMaxHeaderBytes() int32 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Config) GetSessionIDTable() string {
|
||||
if x != nil {
|
||||
return x.SessionIDTable
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Config) GetSessionIDLength() *RangeConfig {
|
||||
if x != nil {
|
||||
return x.SessionIDLength
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_transport_internet_splithttp_config_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_transport_internet_splithttp_config_proto_rawDesc = "" +
|
||||
@@ -441,7 +425,7 @@ const file_transport_internet_splithttp_config_proto_rawDesc = "" +
|
||||
"\x0ecMaxReuseTimes\x18\x03 \x01(\v2..xray.transport.internet.splithttp.RangeConfigR\x0ecMaxReuseTimes\x12Z\n" +
|
||||
"\x10hMaxRequestTimes\x18\x04 \x01(\v2..xray.transport.internet.splithttp.RangeConfigR\x10hMaxRequestTimes\x12Z\n" +
|
||||
"\x10hMaxReusableSecs\x18\x05 \x01(\v2..xray.transport.internet.splithttp.RangeConfigR\x10hMaxReusableSecs\x12*\n" +
|
||||
"\x10hKeepAlivePeriod\x18\x06 \x01(\x03R\x10hKeepAlivePeriod\"\xcc\f\n" +
|
||||
"\x10hKeepAlivePeriod\x18\x06 \x01(\x03R\x10hKeepAlivePeriod\"\xc2\v\n" +
|
||||
"\x06Config\x12\x12\n" +
|
||||
"\x04host\x18\x01 \x01(\tR\x04host\x12\x12\n" +
|
||||
"\x04path\x18\x02 \x01(\tR\x04path\x12\x12\n" +
|
||||
@@ -462,17 +446,17 @@ const file_transport_internet_splithttp_config_proto_rawDesc = "" +
|
||||
"\x0exPaddingHeader\x18\x10 \x01(\tR\x0exPaddingHeader\x12,\n" +
|
||||
"\x11xPaddingPlacement\x18\x11 \x01(\tR\x11xPaddingPlacement\x12&\n" +
|
||||
"\x0exPaddingMethod\x18\x12 \x01(\tR\x0exPaddingMethod\x12*\n" +
|
||||
"\x10uplinkHTTPMethod\x18\x13 \x01(\tR\x10uplinkHTTPMethod\x12.\n" +
|
||||
"\x12sessionIDPlacement\x18\x14 \x01(\tR\x12sessionIDPlacement\x12\"\n" +
|
||||
"\fsessionIDKey\x18\x15 \x01(\tR\fsessionIDKey\x12\"\n" +
|
||||
"\x10uplinkHTTPMethod\x18\x13 \x01(\tR\x10uplinkHTTPMethod\x12*\n" +
|
||||
"\x10sessionPlacement\x18\x14 \x01(\tR\x10sessionPlacement\x12\x1e\n" +
|
||||
"\n" +
|
||||
"sessionKey\x18\x15 \x01(\tR\n" +
|
||||
"sessionKey\x12\"\n" +
|
||||
"\fseqPlacement\x18\x16 \x01(\tR\fseqPlacement\x12\x16\n" +
|
||||
"\x06seqKey\x18\x17 \x01(\tR\x06seqKey\x120\n" +
|
||||
"\x13uplinkDataPlacement\x18\x18 \x01(\tR\x13uplinkDataPlacement\x12$\n" +
|
||||
"\ruplinkDataKey\x18\x19 \x01(\tR\ruplinkDataKey\x12X\n" +
|
||||
"\x0fuplinkChunkSize\x18\x1a \x01(\v2..xray.transport.internet.splithttp.RangeConfigR\x0fuplinkChunkSize\x122\n" +
|
||||
"\x14serverMaxHeaderBytes\x18\x1b \x01(\x05R\x14serverMaxHeaderBytes\x12&\n" +
|
||||
"\x0esessionIDTable\x18\x1c \x01(\tR\x0esessionIDTable\x12X\n" +
|
||||
"\x0fsessionIDLength\x18\x1d \x01(\v2..xray.transport.internet.splithttp.RangeConfigR\x0fsessionIDLength\x1a:\n" +
|
||||
"\x14serverMaxHeaderBytes\x18\x1b \x01(\x05R\x14serverMaxHeaderBytes\x1a:\n" +
|
||||
"\fHeadersEntry\x12\x10\n" +
|
||||
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
||||
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B\x85\x01\n" +
|
||||
@@ -512,12 +496,11 @@ var file_transport_internet_splithttp_config_proto_depIdxs = []int32{
|
||||
1, // 10: xray.transport.internet.splithttp.Config.xmux:type_name -> xray.transport.internet.splithttp.XmuxConfig
|
||||
4, // 11: xray.transport.internet.splithttp.Config.downloadSettings:type_name -> xray.transport.internet.StreamConfig
|
||||
0, // 12: xray.transport.internet.splithttp.Config.uplinkChunkSize:type_name -> xray.transport.internet.splithttp.RangeConfig
|
||||
0, // 13: xray.transport.internet.splithttp.Config.sessionIDLength:type_name -> xray.transport.internet.splithttp.RangeConfig
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
func init() { file_transport_internet_splithttp_config_proto_init() }
|
||||
|
||||
@@ -42,14 +42,12 @@ message Config {
|
||||
string xPaddingPlacement = 17;
|
||||
string xPaddingMethod = 18;
|
||||
string uplinkHTTPMethod = 19;
|
||||
string sessionIDPlacement = 20;
|
||||
string sessionIDKey = 21;
|
||||
string sessionPlacement = 20;
|
||||
string sessionKey = 21;
|
||||
string seqPlacement = 22;
|
||||
string seqKey = 23;
|
||||
string uplinkDataPlacement = 24;
|
||||
string uplinkDataKey = 25;
|
||||
RangeConfig uplinkChunkSize = 26;
|
||||
int32 serverMaxHeaderBytes = 27;
|
||||
string sessionIDTable = 28;
|
||||
RangeConfig sessionIDLength = 29;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/net/cnc"
|
||||
"github.com/xtls/xray-core/common/signal/done"
|
||||
"github.com/xtls/xray-core/common/uuid"
|
||||
"github.com/xtls/xray-core/transport/internet"
|
||||
"github.com/xtls/xray-core/transport/internet/browser_dialer"
|
||||
"github.com/xtls/xray-core/transport/internet/hysteria/congestion"
|
||||
@@ -209,8 +210,6 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea
|
||||
switch c := conn.(type) {
|
||||
case *internet.PacketConnWrapper:
|
||||
pktConn = c.PacketConn
|
||||
case *cnc.Connection:
|
||||
pktConn = &internet.FakePacketConn{Conn: c}
|
||||
default:
|
||||
panic(reflect.TypeOf(c))
|
||||
}
|
||||
@@ -220,30 +219,36 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea
|
||||
|
||||
var pktConn net.PacketConn
|
||||
var udpAddr *net.UDPAddr
|
||||
var index int
|
||||
|
||||
if len(quicParams.UdpHop.Ports) > 0 {
|
||||
index = rand.Intn(len(quicParams.UdpHop.Ports))
|
||||
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 {
|
||||
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 = conn.RemoteAddr().(*net.UDPAddr)
|
||||
default:
|
||||
panic(reflect.TypeOf(c))
|
||||
}
|
||||
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)
|
||||
} 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().(*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 streamSettings.UdpmaskManager != nil {
|
||||
@@ -259,7 +264,6 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
context.AfterFunc(conn.Context(), func() { pktConn.Close() })
|
||||
|
||||
switch quicParams.Congestion {
|
||||
case "reno":
|
||||
@@ -372,7 +376,8 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
|
||||
|
||||
sessionId := ""
|
||||
if mode != "stream-one" {
|
||||
sessionId = transportConfiguration.GenerateSessionID()
|
||||
sessionIdUuid := uuid.New()
|
||||
sessionId = sessionIdUuid.String()
|
||||
}
|
||||
|
||||
errors.LogInfo(ctx, fmt.Sprintf("XHTTP is dialing to %s, mode %s, HTTP version %s, host %s", dest, mode, httpVersion, requestURL.Host))
|
||||
@@ -426,10 +431,10 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
|
||||
}
|
||||
|
||||
if xmuxClient != nil {
|
||||
xmuxClient.AddRunning()
|
||||
xmuxClient.OpenUsage.Add(1)
|
||||
}
|
||||
if xmuxClient2 != nil && xmuxClient2 != xmuxClient {
|
||||
xmuxClient2.AddRunning()
|
||||
xmuxClient2.OpenUsage.Add(1)
|
||||
}
|
||||
var closed atomic.Int32
|
||||
|
||||
@@ -441,10 +446,10 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
|
||||
return
|
||||
}
|
||||
if xmuxClient != nil {
|
||||
xmuxClient.DoneRunning()
|
||||
xmuxClient.OpenUsage.Add(-1)
|
||||
}
|
||||
if xmuxClient2 != nil && xmuxClient2 != xmuxClient {
|
||||
xmuxClient2.DoneRunning()
|
||||
xmuxClient2.OpenUsage.Add(-1)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -146,7 +146,6 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
obfsPaddingAccepted := h.config.XPaddingObfsMode && paddingValue != ""
|
||||
|
||||
sessionId, seqStr := h.config.ExtractMetaFromRequest(request, h.path)
|
||||
|
||||
@@ -156,6 +155,17 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req
|
||||
return
|
||||
}
|
||||
|
||||
var forwardedAddrs []net.Address
|
||||
if h.socketSettings != nil && len(h.socketSettings.TrustedXForwardedFor) > 0 {
|
||||
for _, key := range h.socketSettings.TrustedXForwardedFor {
|
||||
if len(request.Header.Values(key)) > 0 {
|
||||
forwardedAddrs = http_proto.ParseXForwardedFor(request.Header)
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
forwardedAddrs = http_proto.ParseXForwardedFor(request.Header)
|
||||
}
|
||||
var remoteAddr net.Addr
|
||||
var err error
|
||||
remoteAddr, err = net.ResolveTCPAddr("tcp", request.RemoteAddr)
|
||||
@@ -171,11 +181,12 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req
|
||||
Port: remoteAddr.(*net.TCPAddr).Port,
|
||||
}
|
||||
}
|
||||
var trustedXFF []string
|
||||
if h.socketSettings != nil {
|
||||
trustedXFF = h.socketSettings.TrustedXForwardedFor
|
||||
if len(forwardedAddrs) > 0 && forwardedAddrs[0].Family().IsIP() {
|
||||
remoteAddr = &net.TCPAddr{
|
||||
IP: forwardedAddrs[0].IP(),
|
||||
Port: 0,
|
||||
}
|
||||
}
|
||||
remoteAddr = http_proto.ApplyTrustedXForwardedFor(request.Header, trustedXFF, remoteAddr)
|
||||
|
||||
var currentSession *httpSession
|
||||
if sessionId != "" {
|
||||
@@ -216,8 +227,8 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req
|
||||
writer.Header().Set("Cache-Control", "no-store")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
scStreamUpServerSecs := h.config.GetNormalizedScStreamUpServerSecs()
|
||||
hasLegacyRefererCompatMarker := request.Header.Get("Referer") != ""
|
||||
if (hasLegacyRefererCompatMarker || obfsPaddingAccepted) && scStreamUpServerSecs.To > 0 {
|
||||
referrer := request.Header.Get("Referer")
|
||||
if referrer != "" && scStreamUpServerSecs.To > 0 {
|
||||
go func() {
|
||||
for {
|
||||
_, err := httpSC.Write(bytes.Repeat([]byte{'X'}, int(h.config.GetNormalizedXPaddingBytes().rand())))
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
)
|
||||
|
||||
@@ -18,27 +17,10 @@ type XmuxConn interface {
|
||||
|
||||
type XmuxClient struct {
|
||||
XmuxConn XmuxConn
|
||||
Running atomic.Int32
|
||||
OpenUsage atomic.Int32
|
||||
leftUsage int32
|
||||
LeftRequests atomic.Int32
|
||||
UnreusableAt time.Time
|
||||
NotUsed atomic.Bool
|
||||
}
|
||||
|
||||
func (c *XmuxClient) AddRunning() {
|
||||
c.Running.Add(1)
|
||||
}
|
||||
|
||||
func (c *XmuxClient) DoneRunning() {
|
||||
c.Running.Add(-1)
|
||||
c.maybeClose()
|
||||
}
|
||||
|
||||
// close the XmuxConn if it is not used and has no running requests
|
||||
func (c *XmuxClient) maybeClose() {
|
||||
if c.NotUsed.Load() && c.Running.Load() <= 0 {
|
||||
common.Close(c.XmuxConn)
|
||||
}
|
||||
}
|
||||
|
||||
type XmuxManager struct {
|
||||
@@ -86,12 +68,10 @@ func (m *XmuxManager) GetXmuxClient(ctx context.Context) *XmuxClient { // when l
|
||||
xmuxClient.LeftRequests.Load() <= 0 ||
|
||||
(xmuxClient.UnreusableAt != time.Time{} && time.Now().After(xmuxClient.UnreusableAt)) {
|
||||
errors.LogDebug(ctx, "XMUX: removing xmuxClient, IsClosed() = ", xmuxClient.XmuxConn.IsClosed(),
|
||||
", Running = ", xmuxClient.Running.Load(),
|
||||
", OpenUsage = ", xmuxClient.OpenUsage.Load(),
|
||||
", leftUsage = ", xmuxClient.leftUsage,
|
||||
", LeftRequests = ", xmuxClient.LeftRequests.Load(),
|
||||
", UnreusableAt = ", xmuxClient.UnreusableAt)
|
||||
xmuxClient.NotUsed.Store(true)
|
||||
xmuxClient.maybeClose()
|
||||
m.xmuxClients = append(m.xmuxClients[:i], m.xmuxClients[i+1:]...)
|
||||
} else {
|
||||
i++
|
||||
@@ -111,7 +91,7 @@ func (m *XmuxManager) GetXmuxClient(ctx context.Context) *XmuxClient { // when l
|
||||
xmuxClients := make([]*XmuxClient, 0)
|
||||
if m.concurrency > 0 {
|
||||
for _, xmuxClient := range m.xmuxClients {
|
||||
if xmuxClient.Running.Load() < m.concurrency {
|
||||
if xmuxClient.OpenUsage.Load() < m.concurrency {
|
||||
xmuxClients = append(xmuxClients, xmuxClient)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ func TestMaxConcurrency(t *testing.T) {
|
||||
xmuxClients := make(map[interface{}]struct{})
|
||||
for i := 0; i < 64; i++ {
|
||||
xmuxClient := xmuxManager.GetXmuxClient(context.Background())
|
||||
xmuxClient.AddRunning()
|
||||
xmuxClient.OpenUsage.Add(1)
|
||||
xmuxClients[xmuxClient] = struct{}{}
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ func TestDefault(t *testing.T) {
|
||||
xmuxClients := make(map[interface{}]struct{})
|
||||
for i := 0; i < 64; i++ {
|
||||
xmuxClient := xmuxManager.GetXmuxClient(context.Background())
|
||||
xmuxClient.AddRunning()
|
||||
xmuxClient.OpenUsage.Add(1)
|
||||
xmuxClients[xmuxClient] = struct{}{}
|
||||
}
|
||||
|
||||
|
||||
@@ -88,9 +88,6 @@ func TestDialWithRemoteAddr(t *testing.T) {
|
||||
ProtocolSettings: &Config{
|
||||
Path: "sh",
|
||||
},
|
||||
SocketSettings: &internet.SocketConfig{
|
||||
TrustedXForwardedFor: []string{"X-Forwarded-For"},
|
||||
},
|
||||
}, func(conn stat.Connection) {
|
||||
go func(c stat.Connection) {
|
||||
defer c.Close()
|
||||
|
||||
@@ -176,15 +176,15 @@ func ApplyPaddingToQuery(u *url.URL, key, value string) {
|
||||
u.RawQuery = q.Encode()
|
||||
}
|
||||
|
||||
func (c *Config) GetNormalizedXPaddingBytes() *RangeConfig {
|
||||
func (c *Config) GetNormalizedXPaddingBytes() RangeConfig {
|
||||
if c.XPaddingBytes == nil || c.XPaddingBytes.To == 0 {
|
||||
return &RangeConfig{
|
||||
return RangeConfig{
|
||||
From: 100,
|
||||
To: 1000,
|
||||
}
|
||||
}
|
||||
|
||||
return c.XPaddingBytes
|
||||
return *c.XPaddingBytes
|
||||
}
|
||||
|
||||
func (c *Config) ApplyXPaddingToHeader(h http.Header, config XPaddingConfig) {
|
||||
|
||||
@@ -234,9 +234,6 @@ func dnsQuery(server string, domain string, sockopt *internet.SocketConfig) ([]b
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
// h2c: in config is just for claim
|
||||
// change scheme to https and expect outbound to handle TLS (freedom + tlsSetting)
|
||||
req.URL.Scheme = "https"
|
||||
req.Header.Set("Accept", "application/dns-message")
|
||||
req.Header.Set("Content-Type", "application/dns-message")
|
||||
utils.TryDefaultHeadersWith(req.Header, "fetch")
|
||||
|
||||
@@ -65,12 +65,24 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req
|
||||
return
|
||||
}
|
||||
|
||||
remoteAddr := conn.RemoteAddr()
|
||||
var trustedXFF []string
|
||||
if h.socketSettings != nil {
|
||||
trustedXFF = h.socketSettings.TrustedXForwardedFor
|
||||
var forwardedAddrs []net.Address
|
||||
if h.socketSettings != nil && len(h.socketSettings.TrustedXForwardedFor) > 0 {
|
||||
for _, key := range h.socketSettings.TrustedXForwardedFor {
|
||||
if len(request.Header.Values(key)) > 0 {
|
||||
forwardedAddrs = http_proto.ParseXForwardedFor(request.Header)
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
forwardedAddrs = http_proto.ParseXForwardedFor(request.Header)
|
||||
}
|
||||
remoteAddr := conn.RemoteAddr()
|
||||
if len(forwardedAddrs) > 0 && forwardedAddrs[0].Family().IsIP() {
|
||||
remoteAddr = &net.TCPAddr{
|
||||
IP: forwardedAddrs[0].IP(),
|
||||
Port: int(0),
|
||||
}
|
||||
}
|
||||
remoteAddr = http_proto.ApplyTrustedXForwardedFor(request.Header, trustedXFF, remoteAddr)
|
||||
|
||||
h.ln.addConn(NewConnection(conn, remoteAddr, extraReader, h.ln.config.HeartbeatPeriod))
|
||||
}
|
||||
|
||||
@@ -79,9 +79,6 @@ func TestDialWithRemoteAddr(t *testing.T) {
|
||||
ProtocolSettings: &Config{
|
||||
Path: "ws",
|
||||
},
|
||||
SocketSettings: &internet.SocketConfig{
|
||||
TrustedXForwardedFor: []string{"X-Forwarded-For"},
|
||||
},
|
||||
}, func(conn stat.Connection) {
|
||||
go func(c stat.Connection) {
|
||||
defer c.Close()
|
||||
|
||||
Reference in New Issue
Block a user