Skip to content

Commit efd32b0

Browse files
committed
Enable splice for freedom outbound (downlink only)
- Add outbound name - Add outbound conn in ctx - Refactor splice: it can be turn on from all inbounds and outbounds - Refactor splice: Add splice copy to vless inbound - Fix http error test - Add freedom splice toggle via env var - Populate outbound obj in context - Use CanSpliceCopy to mark a connection - Turn off splice by default
1 parent ae2fa30 commit efd32b0

File tree

32 files changed

+282
-168
lines changed

32 files changed

+282
-168
lines changed

app/dispatcher/default.go

+12-8
Original file line numberDiff line numberDiff line change
@@ -218,11 +218,13 @@ func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destin
218218
if !destination.IsValid() {
219219
panic("Dispatcher: Invalid destination.")
220220
}
221-
ob := &session.Outbound{
222-
OriginalTarget: destination,
223-
Target: destination,
221+
ob := session.OutboundFromContext(ctx)
222+
if ob == nil {
223+
ob = &session.Outbound{}
224+
ctx = session.ContextWithOutbound(ctx, ob)
224225
}
225-
ctx = session.ContextWithOutbound(ctx, ob)
226+
ob.OriginalTarget = destination
227+
ob.Target = destination
226228
content := session.ContentFromContext(ctx)
227229
if content == nil {
228230
content = new(session.Content)
@@ -271,11 +273,13 @@ func (d *DefaultDispatcher) DispatchLink(ctx context.Context, destination net.De
271273
if !destination.IsValid() {
272274
return newError("Dispatcher: Invalid destination.")
273275
}
274-
ob := &session.Outbound{
275-
OriginalTarget: destination,
276-
Target: destination,
276+
ob := session.OutboundFromContext(ctx)
277+
if ob == nil {
278+
ob = &session.Outbound{}
279+
ctx = session.ContextWithOutbound(ctx, ob)
277280
}
278-
ctx = session.ContextWithOutbound(ctx, ob)
281+
ob.OriginalTarget = destination
282+
ob.Target = destination
279283
content := session.ContentFromContext(ctx)
280284
if content == nil {
281285
content = new(session.Content)

app/proxyman/inbound/worker.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ func (w *tcpWorker) callback(conn stat.Connection) {
6060
sid := session.NewID()
6161
ctx = session.ContextWithID(ctx, sid)
6262

63+
var outbound = &session.Outbound{}
6364
if w.recvOrigDest {
6465
var dest net.Destination
6566
switch getTProxyType(w.stream) {
@@ -74,11 +75,10 @@ func (w *tcpWorker) callback(conn stat.Connection) {
7475
dest = net.DestinationFromAddr(conn.LocalAddr())
7576
}
7677
if dest.IsValid() {
77-
ctx = session.ContextWithOutbound(ctx, &session.Outbound{
78-
Target: dest,
79-
})
78+
outbound.Target = dest
8079
}
8180
}
81+
ctx = session.ContextWithOutbound(ctx, outbound)
8282

8383
if w.uplinkCounter != nil || w.downlinkCounter != nil {
8484
conn = &stat.CounterConnection{

app/proxyman/outbound/handler.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,12 @@ func (h *Handler) Dial(ctx context.Context, dest net.Destination) (stat.Connecti
274274
}
275275

276276
conn, err := internet.Dial(ctx, dest, h.streamSettings)
277-
return h.getStatCouterConnection(conn), err
277+
conn = h.getStatCouterConnection(conn)
278+
outbound := session.OutboundFromContext(ctx)
279+
if outbound != nil {
280+
outbound.Conn = conn
281+
}
282+
return conn, err
278283
}
279284

280285
func (h *Handler) getStatCouterConnection(conn stat.Connection) stat.Connection {

common/buf/copy.go

+12
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66

77
"github.com/xtls/xray-core/common/errors"
88
"github.com/xtls/xray-core/common/signal"
9+
"github.com/xtls/xray-core/features/stats"
910
)
1011

1112
type dataHandler func(MultiBuffer)
@@ -40,6 +41,17 @@ func CountSize(sc *SizeCounter) CopyOption {
4041
}
4142
}
4243

44+
// AddToStatCounter a CopyOption add to stat counter
45+
func AddToStatCounter(sc stats.Counter) CopyOption {
46+
return func(handler *copyHandler) {
47+
handler.onData = append(handler.onData, func(b MultiBuffer) {
48+
if sc != nil {
49+
sc.Add(int64(b.Len()))
50+
}
51+
})
52+
}
53+
}
54+
4355
type readError struct {
4456
error
4557
}

common/session/session.go

+14
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,16 @@ type Inbound struct {
5050
Conn net.Conn
5151
// Timer of the inbound buf copier. May be nil.
5252
Timer *signal.ActivityTimer
53+
// CanSpliceCopy is a property for this connection, set by both inbound and outbound
54+
// 1 = can, 2 = after processing protocol info should be able to, 3 = cannot
55+
CanSpliceCopy int
56+
}
57+
58+
func(i *Inbound) SetCanSpliceCopy(canSpliceCopy int) int {
59+
if canSpliceCopy > i.CanSpliceCopy {
60+
i.CanSpliceCopy = canSpliceCopy
61+
}
62+
return i.CanSpliceCopy
5363
}
5464

5565
// Outbound is the metadata of an outbound connection.
@@ -60,6 +70,10 @@ type Outbound struct {
6070
RouteTarget net.Destination
6171
// Gateway address
6272
Gateway net.Address
73+
// Name of the outbound proxy that handles the connection.
74+
Name string
75+
// Conn is actually internet.Connection. May be nil. It is currently nil for outbound with proxySettings
76+
Conn net.Conn
6377
}
6478

6579
// SniffingRequest controls the behavior of content sniffing.

proxy/blackhole/blackhole.go

+6
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"time"
99

1010
"github.com/xtls/xray-core/common"
11+
"github.com/xtls/xray-core/common/session"
1112
"github.com/xtls/xray-core/transport"
1213
"github.com/xtls/xray-core/transport/internet"
1314
)
@@ -30,6 +31,11 @@ func New(ctx context.Context, config *Config) (*Handler, error) {
3031

3132
// Process implements OutboundHandler.Dispatch().
3233
func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error {
34+
outbound := session.OutboundFromContext(ctx)
35+
if outbound != nil {
36+
outbound.Name = "blackhole"
37+
}
38+
3339
nBytes := h.response.WriteTo(link.Writer)
3440
if nBytes > 0 {
3541
// Sleep a little here to make sure the response is sent to client.

proxy/dns/dns.go

+1
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, d internet.
9696
if outbound == nil || !outbound.Target.IsValid() {
9797
return newError("invalid outbound")
9898
}
99+
outbound.Name = "dns"
99100

100101
srcNetwork := outbound.Target.Network
101102

proxy/dokodemo/dokodemo.go

+4-5
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,10 @@ func (d *DokodemoDoor) Process(ctx context.Context, network net.Network, conn st
102102
}
103103

104104
inbound := session.InboundFromContext(ctx)
105-
if inbound != nil {
106-
inbound.Name = "dokodemo-door"
107-
inbound.User = &protocol.MemoryUser{
108-
Level: d.config.UserLevel,
109-
}
105+
inbound.Name = "dokodemo-door"
106+
inbound.SetCanSpliceCopy(1)
107+
inbound.User = &protocol.MemoryUser{
108+
Level: d.config.UserLevel,
110109
}
111110

112111
ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{

proxy/errors.generated.go

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package proxy
2+
3+
import "github.com/xtls/xray-core/common/errors"
4+
5+
type errPathObjHolder struct{}
6+
7+
func newError(values ...interface{}) *errors.Error {
8+
return errors.New(values...).WithPathObj(errPathObjHolder{})
9+
}

proxy/freedom/freedom.go

+21-6
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/xtls/xray-core/common/buf"
1414
"github.com/xtls/xray-core/common/dice"
1515
"github.com/xtls/xray-core/common/net"
16+
"github.com/xtls/xray-core/common/platform"
1617
"github.com/xtls/xray-core/common/retry"
1718
"github.com/xtls/xray-core/common/session"
1819
"github.com/xtls/xray-core/common/signal"
@@ -21,11 +22,14 @@ import (
2122
"github.com/xtls/xray-core/features/dns"
2223
"github.com/xtls/xray-core/features/policy"
2324
"github.com/xtls/xray-core/features/stats"
25+
"github.com/xtls/xray-core/proxy"
2426
"github.com/xtls/xray-core/transport"
2527
"github.com/xtls/xray-core/transport/internet"
2628
"github.com/xtls/xray-core/transport/internet/stat"
2729
)
2830

31+
var useSplice bool
32+
2933
func init() {
3034
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
3135
h := new(Handler)
@@ -36,6 +40,12 @@ func init() {
3640
}
3741
return h, nil
3842
}))
43+
const defaultFlagValue = "NOT_DEFINED_AT_ALL"
44+
value := platform.NewEnvFlag("xray.buf.splice").GetValue(func() string { return defaultFlagValue })
45+
switch value {
46+
case "auto", "enable":
47+
useSplice = true
48+
}
3949
}
4050

4151
// Handler handles Freedom connections.
@@ -107,6 +117,11 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
107117
if outbound == nil || !outbound.Target.IsValid() {
108118
return newError("target not specified.")
109119
}
120+
outbound.Name = "freedom"
121+
inbound := session.InboundFromContext(ctx)
122+
if inbound != nil {
123+
inbound.SetCanSpliceCopy(1)
124+
}
110125
destination := outbound.Target
111126
UDPOverride := net.UDPDestination(nil, 0)
112127
if h.config.DestinationOverride != nil {
@@ -195,17 +210,17 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
195210

196211
responseDone := func() error {
197212
defer timer.SetTimeout(plcy.Timeouts.UplinkOnly)
198-
199-
var reader buf.Reader
200213
if destination.Network == net.Network_TCP {
201-
reader = buf.NewReader(conn)
202-
} else {
203-
reader = NewPacketReader(conn, UDPOverride)
214+
var writeConn net.Conn
215+
if inbound := session.InboundFromContext(ctx); inbound != nil && inbound.Conn != nil && useSplice {
216+
writeConn = inbound.Conn
217+
}
218+
return proxy.CopyRawConnIfExist(ctx, conn, writeConn, link.Writer, timer)
204219
}
220+
reader := NewPacketReader(conn, UDPOverride)
205221
if err := buf.Copy(reader, output, buf.UpdateActivity(timer)); err != nil {
206222
return newError("failed to process response").Base(err)
207223
}
208-
209224
return nil
210225
}
211226

proxy/http/client.go

+5
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,11 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter
7373
if outbound == nil || !outbound.Target.IsValid() {
7474
return newError("target not specified.")
7575
}
76+
outbound.Name = "http"
77+
inbound := session.InboundFromContext(ctx)
78+
if inbound != nil {
79+
inbound.SetCanSpliceCopy(2)
80+
}
7681
target := outbound.Target
7782
targetAddr := target.NetAddr()
7883

proxy/http/server.go

+4-5
Original file line numberDiff line numberDiff line change
@@ -84,11 +84,10 @@ type readerOnly struct {
8484

8585
func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Connection, dispatcher routing.Dispatcher) error {
8686
inbound := session.InboundFromContext(ctx)
87-
if inbound != nil {
88-
inbound.Name = "http"
89-
inbound.User = &protocol.MemoryUser{
90-
Level: s.config.UserLevel,
91-
}
87+
inbound.Name = "http"
88+
inbound.SetCanSpliceCopy(2)
89+
inbound.User = &protocol.MemoryUser{
90+
Level: s.config.UserLevel,
9291
}
9392

9493
reader := bufio.NewReaderSize(readerOnly{conn}, buf.Size)

proxy/loopback/loopback.go

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ func (l *Loopback) Process(ctx context.Context, link *transport.Link, _ internet
2626
if outbound == nil || !outbound.Target.IsValid() {
2727
return newError("target not specified.")
2828
}
29+
outbound.Name = "loopback"
2930
destination := outbound.Target
3031

3132
newError("opening connection to ", destination).WriteToLog(session.ExportIDToError(ctx))

proxy/proxy.go

+86
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,24 @@ package proxy
77

88
import (
99
"context"
10+
gotls "crypto/tls"
11+
"io"
12+
"runtime"
1013

14+
"github.com/pires/go-proxyproto"
15+
"github.com/xtls/xray-core/common/buf"
16+
"github.com/xtls/xray-core/common/errors"
1117
"github.com/xtls/xray-core/common/net"
1218
"github.com/xtls/xray-core/common/protocol"
19+
"github.com/xtls/xray-core/common/session"
20+
"github.com/xtls/xray-core/common/signal"
1321
"github.com/xtls/xray-core/features/routing"
22+
"github.com/xtls/xray-core/features/stats"
1423
"github.com/xtls/xray-core/transport"
1524
"github.com/xtls/xray-core/transport/internet"
25+
"github.com/xtls/xray-core/transport/internet/reality"
1626
"github.com/xtls/xray-core/transport/internet/stat"
27+
"github.com/xtls/xray-core/transport/internet/tls"
1728
)
1829

1930
// An Inbound processes inbound connections.
@@ -47,3 +58,78 @@ type GetInbound interface {
4758
type GetOutbound interface {
4859
GetOutbound() Outbound
4960
}
61+
62+
// UnwrapRawConn support unwrap stats, tls, utls, reality and proxyproto conn and get raw tcp conn from it
63+
func UnwrapRawConn(conn net.Conn) (net.Conn, stats.Counter, stats.Counter) {
64+
var readCounter, writerCounter stats.Counter
65+
if conn != nil {
66+
statConn, ok := conn.(*stat.CounterConnection)
67+
if ok {
68+
conn = statConn.Connection
69+
readCounter = statConn.ReadCounter
70+
writerCounter = statConn.WriteCounter
71+
}
72+
if xc, ok := conn.(*gotls.Conn); ok {
73+
conn = xc.NetConn()
74+
} else if utlsConn, ok := conn.(*tls.UConn); ok {
75+
conn = utlsConn.NetConn()
76+
} else if realityConn, ok := conn.(*reality.Conn); ok {
77+
conn = realityConn.NetConn()
78+
} else if realityUConn, ok := conn.(*reality.UConn); ok {
79+
conn = realityUConn.NetConn()
80+
}
81+
if pc, ok := conn.(*proxyproto.Conn); ok {
82+
conn = pc.Raw()
83+
// 8192 > 4096, there is no need to process pc's bufReader
84+
}
85+
}
86+
return conn, readCounter, writerCounter
87+
}
88+
89+
// CopyRawConnIfExist use the most efficient copy method.
90+
// - If caller don't want to turn on splice, do not pass in both reader conn and writer conn
91+
// - writer are from *transport.Link
92+
func CopyRawConnIfExist(ctx context.Context, readerConn net.Conn, writerConn net.Conn, writer buf.Writer, timer signal.ActivityUpdater) error {
93+
readerConn, readCounter, _ := UnwrapRawConn(readerConn)
94+
writerConn, _, writeCounter := UnwrapRawConn(writerConn)
95+
reader := buf.NewReader(readerConn)
96+
if inbound := session.InboundFromContext(ctx); inbound != nil {
97+
if tc, ok := writerConn.(*net.TCPConn); ok && readerConn != nil && writerConn != nil && (runtime.GOOS == "linux" || runtime.GOOS == "android") {
98+
for inbound.CanSpliceCopy != 3 {
99+
if inbound.CanSpliceCopy == 1 {
100+
newError("CopyRawConn splice").WriteToLog(session.ExportIDToError(ctx))
101+
runtime.Gosched() // necessary
102+
w, err := tc.ReadFrom(readerConn)
103+
if readCounter != nil {
104+
readCounter.Add(w)
105+
}
106+
if writeCounter != nil {
107+
writeCounter.Add(w)
108+
}
109+
if err != nil && errors.Cause(err) != io.EOF {
110+
return err
111+
}
112+
return nil
113+
}
114+
buffer, err := reader.ReadMultiBuffer()
115+
if !buffer.IsEmpty() {
116+
if readCounter != nil {
117+
readCounter.Add(int64(buffer.Len()))
118+
}
119+
timer.Update()
120+
if werr := writer.WriteMultiBuffer(buffer); werr != nil {
121+
return werr
122+
}
123+
}
124+
if err != nil {
125+
return err
126+
}
127+
}
128+
}
129+
}
130+
newError("CopyRawConn readv").WriteToLog(session.ExportIDToError(ctx))
131+
if err := buf.Copy(reader, writer, buf.UpdateActivity(timer), buf.AddToStatCounter(readCounter)); err != nil {
132+
return newError("failed to process response").Base(err)
133+
}
134+
return nil
135+
}

0 commit comments

Comments
 (0)