2020-11-25 14:01:53 +03:00
|
|
|
package outbound
|
|
|
|
|
2020-12-04 04:36:16 +03:00
|
|
|
//go:generate go run github.com/xtls/xray-core/common/errors/errorgen
|
2020-11-25 14:01:53 +03:00
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2021-12-15 03:27:31 +03:00
|
|
|
"crypto/hmac"
|
|
|
|
"crypto/sha256"
|
|
|
|
"hash/crc64"
|
2020-11-25 14:01:53 +03:00
|
|
|
"time"
|
|
|
|
|
2020-12-04 04:36:16 +03:00
|
|
|
"github.com/xtls/xray-core/common"
|
|
|
|
"github.com/xtls/xray-core/common/buf"
|
|
|
|
"github.com/xtls/xray-core/common/net"
|
|
|
|
"github.com/xtls/xray-core/common/platform"
|
|
|
|
"github.com/xtls/xray-core/common/protocol"
|
|
|
|
"github.com/xtls/xray-core/common/retry"
|
|
|
|
"github.com/xtls/xray-core/common/session"
|
|
|
|
"github.com/xtls/xray-core/common/signal"
|
|
|
|
"github.com/xtls/xray-core/common/task"
|
2021-02-11 14:33:08 +03:00
|
|
|
"github.com/xtls/xray-core/common/xudp"
|
2020-12-04 04:36:16 +03:00
|
|
|
core "github.com/xtls/xray-core/core"
|
|
|
|
"github.com/xtls/xray-core/features/policy"
|
|
|
|
"github.com/xtls/xray-core/proxy/vmess"
|
|
|
|
"github.com/xtls/xray-core/proxy/vmess/encoding"
|
|
|
|
"github.com/xtls/xray-core/transport"
|
|
|
|
"github.com/xtls/xray-core/transport/internet"
|
2021-12-15 03:28:47 +03:00
|
|
|
"github.com/xtls/xray-core/transport/internet/stat"
|
2020-11-25 14:01:53 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
// Handler is an outbound connection handler for VMess protocol.
|
|
|
|
type Handler struct {
|
|
|
|
serverList *protocol.ServerList
|
|
|
|
serverPicker protocol.ServerPicker
|
|
|
|
policyManager policy.Manager
|
2021-02-11 18:37:02 +03:00
|
|
|
cone bool
|
2020-11-25 14:01:53 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// New creates a new VMess outbound handler.
|
|
|
|
func New(ctx context.Context, config *Config) (*Handler, error) {
|
|
|
|
serverList := protocol.NewServerList()
|
|
|
|
for _, rec := range config.Receiver {
|
|
|
|
s, err := protocol.NewServerSpecFromPB(rec)
|
|
|
|
if err != nil {
|
|
|
|
return nil, newError("failed to parse server spec").Base(err)
|
|
|
|
}
|
|
|
|
serverList.AddServer(s)
|
|
|
|
}
|
|
|
|
|
|
|
|
v := core.MustFromContext(ctx)
|
|
|
|
handler := &Handler{
|
|
|
|
serverList: serverList,
|
|
|
|
serverPicker: protocol.NewRoundRobinServerPicker(serverList),
|
|
|
|
policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
|
2021-02-11 18:37:02 +03:00
|
|
|
cone: ctx.Value("cone").(bool),
|
2020-11-25 14:01:53 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return handler, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Process implements proxy.Outbound.Process().
|
|
|
|
func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error {
|
|
|
|
var rec *protocol.ServerSpec
|
2021-09-20 15:11:21 +03:00
|
|
|
var conn stat.Connection
|
2020-11-25 14:01:53 +03:00
|
|
|
|
|
|
|
err := retry.ExponentialBackoff(5, 200).On(func() error {
|
|
|
|
rec = h.serverPicker.PickServer()
|
|
|
|
rawConn, err := dialer.Dial(ctx, rec.Destination())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
conn = rawConn
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return newError("failed to find an available destination").Base(err).AtWarning()
|
|
|
|
}
|
|
|
|
defer conn.Close()
|
|
|
|
|
|
|
|
outbound := session.OutboundFromContext(ctx)
|
|
|
|
if outbound == nil || !outbound.Target.IsValid() {
|
|
|
|
return newError("target not specified").AtError()
|
|
|
|
}
|
|
|
|
|
|
|
|
target := outbound.Target
|
Quic related improvements (#915)
* DialSystem for Quic
DialSystem() is needed in case of Android client,
where the raw conn is protected for vpn service
* Fix client dialer log
Log such as:
tunneling request to tcp:www.google.com:80 via tcp:x.x.x.x:443
the second "tcp" is misleading when using mKcp or quic transport
Remove the second "tcp" and add the correct logging for transport dialer:
- transport/internet/tcp: dialing TCP to tcp:x.x.x.x:443
- transport/internet/quic: dialing quic to udp:x.x.x.x:443
* Quic new stream allocation mode
Currently this is how Quic works: client muxing all tcp and udp traffic through a single session, when there are more than 32 running streams in the session,
the next stream request will fail and open with a new session (port). Imagine lineup the session from left to right:
|
| |
| | |
As the streams finishes, we still open stream from the left, original session. So the base session will always be there and new sessions on the right come and go.
However, either due to QOS or bugs in Quic implementation, the traffic "wear out" the base session. It will become slower and in the end not receiving any data from server side.
I couldn't figure out a solution for this problem at the moment, as a workaround:
| |
| | |
| | |
I came up with this new stream allocation mode, that it will never open new streams in the old sessions, but only from current or new session from right.
The keeplive config is turned off from server and client side. This way old sessions will natually close and new sessions keep generating.
Note the frequency of new session is still controlled by the server side. Server can assign a large max stream limit. In this case the new allocation mode will be similar to the current mode.
2022-01-29 02:11:30 +03:00
|
|
|
newError("tunneling request to ", target, " via ", rec.Destination().NetAddr()).WriteToLog(session.ExportIDToError(ctx))
|
2020-11-25 14:01:53 +03:00
|
|
|
|
|
|
|
command := protocol.RequestCommandTCP
|
|
|
|
if target.Network == net.Network_UDP {
|
|
|
|
command = protocol.RequestCommandUDP
|
|
|
|
}
|
|
|
|
if target.Address.Family().IsDomain() && target.Address.Domain() == "v1.mux.cool" {
|
|
|
|
command = protocol.RequestCommandMux
|
|
|
|
}
|
|
|
|
|
|
|
|
user := rec.PickUser()
|
|
|
|
request := &protocol.RequestHeader{
|
|
|
|
Version: encoding.Version,
|
|
|
|
User: user,
|
|
|
|
Command: command,
|
|
|
|
Address: target.Address,
|
|
|
|
Port: target.Port,
|
|
|
|
Option: protocol.RequestOptionChunkStream,
|
|
|
|
}
|
|
|
|
|
|
|
|
account := request.User.Account.(*vmess.MemoryAccount)
|
|
|
|
request.Security = account.Security
|
|
|
|
|
|
|
|
if request.Security == protocol.SecurityType_AES128_GCM || request.Security == protocol.SecurityType_NONE || request.Security == protocol.SecurityType_CHACHA20_POLY1305 {
|
|
|
|
request.Option.Set(protocol.RequestOptionChunkMasking)
|
|
|
|
}
|
|
|
|
|
|
|
|
if shouldEnablePadding(request.Security) && request.Option.Has(protocol.RequestOptionChunkMasking) {
|
|
|
|
request.Option.Set(protocol.RequestOptionGlobalPadding)
|
|
|
|
}
|
|
|
|
|
2021-03-05 11:41:18 +03:00
|
|
|
if request.Security == protocol.SecurityType_ZERO {
|
|
|
|
request.Security = protocol.SecurityType_NONE
|
|
|
|
request.Option.Clear(protocol.RequestOptionChunkStream)
|
|
|
|
request.Option.Clear(protocol.RequestOptionChunkMasking)
|
|
|
|
}
|
|
|
|
|
2021-04-29 01:29:42 +03:00
|
|
|
if account.AuthenticatedLengthExperiment {
|
|
|
|
request.Option.Set(protocol.RequestOptionAuthenticatedLength)
|
|
|
|
}
|
|
|
|
|
2020-11-25 14:01:53 +03:00
|
|
|
input := link.Reader
|
|
|
|
output := link.Writer
|
|
|
|
|
|
|
|
isAEAD := false
|
|
|
|
if !aeadDisabled && len(account.AlterIDs) == 0 {
|
|
|
|
isAEAD = true
|
|
|
|
}
|
|
|
|
|
2021-12-15 03:27:31 +03:00
|
|
|
hashkdf := hmac.New(sha256.New, []byte("VMessBF"))
|
|
|
|
hashkdf.Write(account.ID.Bytes())
|
|
|
|
|
|
|
|
behaviorSeed := crc64.Checksum(hashkdf.Sum(nil), crc64.MakeTable(crc64.ISO))
|
|
|
|
|
2023-04-06 13:21:35 +03:00
|
|
|
var newCtx context.Context
|
|
|
|
var newCancel context.CancelFunc
|
|
|
|
if session.TimeoutOnlyFromContext(ctx) {
|
|
|
|
newCtx, newCancel = context.WithCancel(context.Background())
|
|
|
|
}
|
|
|
|
|
2021-12-15 03:27:31 +03:00
|
|
|
session := encoding.NewClientSession(ctx, isAEAD, protocol.DefaultIDHash, int64(behaviorSeed))
|
2020-11-25 14:01:53 +03:00
|
|
|
sessionPolicy := h.policyManager.ForLevel(request.User.Level)
|
|
|
|
|
|
|
|
ctx, cancel := context.WithCancel(ctx)
|
2023-04-06 13:21:35 +03:00
|
|
|
timer := signal.CancelAfterInactivity(ctx, func() {
|
|
|
|
cancel()
|
|
|
|
if newCancel != nil {
|
|
|
|
newCancel()
|
|
|
|
}
|
|
|
|
}, sessionPolicy.Timeouts.ConnectionIdle)
|
2020-11-25 14:01:53 +03:00
|
|
|
|
2021-03-08 21:36:45 +03:00
|
|
|
if request.Command == protocol.RequestCommandUDP && h.cone && request.Port != 53 && request.Port != 443 {
|
2021-02-11 04:28:21 +03:00
|
|
|
request.Command = protocol.RequestCommandMux
|
|
|
|
request.Address = net.DomainAddress("v1.mux.cool")
|
|
|
|
request.Port = net.Port(666)
|
|
|
|
}
|
|
|
|
|
2020-11-25 14:01:53 +03:00
|
|
|
requestDone := func() error {
|
|
|
|
defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)
|
|
|
|
|
|
|
|
writer := buf.NewBufferedWriter(buf.NewWriter(conn))
|
|
|
|
if err := session.EncodeRequestHeader(request, writer); err != nil {
|
|
|
|
return newError("failed to encode request").Base(err).AtWarning()
|
|
|
|
}
|
|
|
|
|
2022-06-13 01:29:09 +03:00
|
|
|
bodyWriter, err := session.EncodeRequestBody(request, writer)
|
|
|
|
if err != nil {
|
|
|
|
return newError("failed to start encoding").Base(err)
|
|
|
|
}
|
2021-02-11 04:28:21 +03:00
|
|
|
bodyWriter2 := bodyWriter
|
|
|
|
if request.Command == protocol.RequestCommandMux && request.Port == 666 {
|
2023-04-06 13:21:35 +03:00
|
|
|
bodyWriter = xudp.NewPacketWriter(bodyWriter, target, xudp.GetGlobalID(ctx))
|
2021-02-11 04:28:21 +03:00
|
|
|
}
|
2020-11-25 14:01:53 +03:00
|
|
|
if err := buf.CopyOnceTimeout(input, bodyWriter, time.Millisecond*100); err != nil && err != buf.ErrNotTimeoutReader && err != buf.ErrReadTimeout {
|
|
|
|
return newError("failed to write first payload").Base(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := writer.SetBuffered(false); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := buf.Copy(input, bodyWriter, buf.UpdateActivity(timer)); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-04-29 16:28:52 +03:00
|
|
|
if request.Option.Has(protocol.RequestOptionChunkStream) && !account.NoTerminationSignal {
|
2021-02-11 04:28:21 +03:00
|
|
|
if err := bodyWriter2.WriteMultiBuffer(buf.MultiBuffer{}); err != nil {
|
2020-11-25 14:01:53 +03:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
responseDone := func() error {
|
|
|
|
defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)
|
|
|
|
|
|
|
|
reader := &buf.BufferedReader{Reader: buf.NewReader(conn)}
|
|
|
|
header, err := session.DecodeResponseHeader(reader)
|
|
|
|
if err != nil {
|
|
|
|
return newError("failed to read header").Base(err)
|
|
|
|
}
|
|
|
|
h.handleCommand(rec.Destination(), header.Command)
|
|
|
|
|
2022-06-13 01:29:09 +03:00
|
|
|
bodyReader, err := session.DecodeResponseBody(request, reader)
|
|
|
|
if err != nil {
|
|
|
|
return newError("failed to start encoding response").Base(err)
|
|
|
|
}
|
2021-02-11 04:28:21 +03:00
|
|
|
if request.Command == protocol.RequestCommandMux && request.Port == 666 {
|
2021-02-11 14:33:08 +03:00
|
|
|
bodyReader = xudp.NewPacketReader(&buf.BufferedReader{Reader: bodyReader})
|
2021-02-11 04:28:21 +03:00
|
|
|
}
|
2020-11-25 14:01:53 +03:00
|
|
|
|
|
|
|
return buf.Copy(bodyReader, output, buf.UpdateActivity(timer))
|
|
|
|
}
|
|
|
|
|
2023-04-06 13:21:35 +03:00
|
|
|
if newCtx != nil {
|
|
|
|
ctx = newCtx
|
|
|
|
}
|
|
|
|
|
2021-10-19 19:57:14 +03:00
|
|
|
responseDonePost := task.OnSuccess(responseDone, task.Close(output))
|
2020-11-25 14:01:53 +03:00
|
|
|
if err := task.Run(ctx, requestDone, responseDonePost); err != nil {
|
|
|
|
return newError("connection ends").Base(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
enablePadding = false
|
|
|
|
aeadDisabled = false
|
|
|
|
)
|
|
|
|
|
|
|
|
func shouldEnablePadding(s protocol.SecurityType) bool {
|
|
|
|
return enablePadding || s == protocol.SecurityType_AES128_GCM || s == protocol.SecurityType_CHACHA20_POLY1305 || s == protocol.SecurityType_AUTO
|
|
|
|
}
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
|
|
|
return New(ctx, config.(*Config))
|
|
|
|
}))
|
|
|
|
|
|
|
|
const defaultFlagValue = "NOT_DEFINED_AT_ALL"
|
|
|
|
|
|
|
|
paddingValue := platform.NewEnvFlag("xray.vmess.padding").GetValue(func() string { return defaultFlagValue })
|
|
|
|
if paddingValue != defaultFlagValue {
|
|
|
|
enablePadding = true
|
|
|
|
}
|
|
|
|
|
|
|
|
isAeadDisabled := platform.NewEnvFlag("xray.vmess.aead.disabled").GetValue(func() string { return defaultFlagValue })
|
|
|
|
if isAeadDisabled == "true" {
|
|
|
|
aeadDisabled = true
|
|
|
|
}
|
|
|
|
}
|