chore: support TFO for outbounds
This commit is contained in:
parent
75680c5866
commit
7d524668e0
12 changed files with 144 additions and 1 deletions
|
@ -140,10 +140,15 @@ func (b *Base) DialOptions(opts ...dialer.Option) []dialer.Option {
|
|||
default:
|
||||
}
|
||||
|
||||
if b.tfo {
|
||||
opts = append(opts, dialer.WithTFO(true))
|
||||
}
|
||||
|
||||
return opts
|
||||
}
|
||||
|
||||
type BasicOption struct {
|
||||
TFO bool `proxy:"tfo,omitempty" group:"tfo,omitempty"`
|
||||
Interface string `proxy:"interface-name,omitempty" group:"interface-name,omitempty"`
|
||||
RoutingMark int `proxy:"routing-mark,omitempty" group:"routing-mark,omitempty"`
|
||||
IPVersion string `proxy:"ip-version,omitempty" group:"ip-version,omitempty"`
|
||||
|
|
|
@ -170,6 +170,7 @@ func NewHttp(option HttpOption) (*Http, error) {
|
|||
name: option.Name,
|
||||
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
||||
tp: C.Http,
|
||||
tfo: option.TFO,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
|
|
|
@ -252,6 +252,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
|||
addr: addr,
|
||||
tp: C.Shadowsocks,
|
||||
udp: option.UDP,
|
||||
tfo: option.TFO,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
|
|
|
@ -163,6 +163,7 @@ func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
|
|||
addr: addr,
|
||||
tp: C.ShadowsocksR,
|
||||
udp: option.UDP,
|
||||
tfo: option.TFO,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
|
|
|
@ -167,6 +167,7 @@ func NewSnell(option SnellOption) (*Snell, error) {
|
|||
addr: addr,
|
||||
tp: C.Snell,
|
||||
udp: option.UDP,
|
||||
tfo: option.TFO,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
|
|
|
@ -182,6 +182,7 @@ func NewSocks5(option Socks5Option) (*Socks5, error) {
|
|||
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
||||
tp: C.Socks5,
|
||||
udp: option.UDP,
|
||||
tfo: option.TFO,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
|
|
|
@ -250,6 +250,7 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
|||
addr: addr,
|
||||
tp: C.Trojan,
|
||||
udp: option.UDP,
|
||||
tfo: option.TFO,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
|
|
|
@ -510,6 +510,7 @@ func NewVless(option VlessOption) (*Vless, error) {
|
|||
tp: C.Vless,
|
||||
udp: option.UDP,
|
||||
xudp: option.XUDP,
|
||||
tfo: option.TFO,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
|
|
|
@ -387,6 +387,7 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
|||
tp: C.Vmess,
|
||||
udp: option.UDP,
|
||||
xudp: option.XUDP,
|
||||
tfo: option.TFO,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
|
|
|
@ -118,7 +118,11 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po
|
|||
return nil, ErrorDisableIPv6
|
||||
}
|
||||
|
||||
return dialer.DialContext(ctx, network, net.JoinHostPort(destination.String(), port))
|
||||
address := net.JoinHostPort(destination.String(), port)
|
||||
if opt.tfo {
|
||||
return dialTFO(ctx, *dialer, network, address)
|
||||
}
|
||||
return dialer.DialContext(ctx, network, address)
|
||||
}
|
||||
|
||||
func singleDialContext(ctx context.Context, network string, address string, opt *option) (net.Conn, error) {
|
||||
|
|
|
@ -18,6 +18,7 @@ type option struct {
|
|||
routingMark int
|
||||
network int
|
||||
prefer int
|
||||
tfo bool
|
||||
resolver resolver.Resolver
|
||||
}
|
||||
|
||||
|
@ -69,6 +70,12 @@ func WithOnlySingleStack(isIPv4 bool) Option {
|
|||
}
|
||||
}
|
||||
|
||||
func WithTFO(tfo bool) Option {
|
||||
return func(opt *option) {
|
||||
opt.tfo = tfo
|
||||
}
|
||||
}
|
||||
|
||||
func WithOption(o option) Option {
|
||||
return func(opt *option) {
|
||||
*opt = o
|
||||
|
|
119
component/dialer/tfo.go
Normal file
119
component/dialer/tfo.go
Normal file
|
@ -0,0 +1,119 @@
|
|||
package dialer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/sagernet/tfo-go"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
type tfoConn struct {
|
||||
net.Conn
|
||||
closed bool
|
||||
dialed chan bool
|
||||
cancel context.CancelFunc
|
||||
ctx context.Context
|
||||
dialFn func(ctx context.Context, earlyData []byte) (net.Conn, error)
|
||||
}
|
||||
|
||||
func (c *tfoConn) Dial(earlyData []byte) (err error) {
|
||||
c.Conn, err = c.dialFn(c.ctx, earlyData)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.dialed <- true
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *tfoConn) Read(b []byte) (n int, err error) {
|
||||
if c.closed {
|
||||
return 0, io.ErrClosedPipe
|
||||
}
|
||||
if c.Conn == nil {
|
||||
select {
|
||||
case <-c.ctx.Done():
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
case <-c.dialed:
|
||||
}
|
||||
}
|
||||
return c.Conn.Read(b)
|
||||
}
|
||||
|
||||
func (c *tfoConn) Write(b []byte) (n int, err error) {
|
||||
if c.closed {
|
||||
return 0, io.ErrClosedPipe
|
||||
}
|
||||
if c.Conn == nil {
|
||||
if err := c.Dial(b); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
return c.Conn.Write(b)
|
||||
}
|
||||
|
||||
func (c *tfoConn) Close() error {
|
||||
c.closed = true
|
||||
c.cancel()
|
||||
if c.Conn == nil {
|
||||
return nil
|
||||
}
|
||||
return c.Conn.Close()
|
||||
}
|
||||
|
||||
func (c *tfoConn) LocalAddr() net.Addr {
|
||||
if c.Conn == nil {
|
||||
return nil
|
||||
}
|
||||
return c.Conn.LocalAddr()
|
||||
}
|
||||
|
||||
func (c *tfoConn) RemoteAddr() net.Addr {
|
||||
if c.Conn == nil {
|
||||
return nil
|
||||
}
|
||||
return c.Conn.RemoteAddr()
|
||||
}
|
||||
|
||||
func (c *tfoConn) SetDeadline(t time.Time) error {
|
||||
if err := c.SetReadDeadline(t); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
func (c *tfoConn) SetReadDeadline(t time.Time) error {
|
||||
if c.Conn == nil {
|
||||
return nil
|
||||
}
|
||||
return c.Conn.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
func (c *tfoConn) SetWriteDeadline(t time.Time) error {
|
||||
if c.Conn == nil {
|
||||
return nil
|
||||
}
|
||||
return c.Conn.SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
func (c *tfoConn) Upstream() any {
|
||||
if c.Conn == nil { // ensure return a nil interface not an interface with nil value
|
||||
return nil
|
||||
}
|
||||
return c.Conn
|
||||
}
|
||||
|
||||
func dialTFO(ctx context.Context, netDialer net.Dialer, network, address string) (net.Conn, error) {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
dialer := tfo.Dialer{Dialer: netDialer, DisableTFO: false}
|
||||
return &tfoConn{
|
||||
dialed: make(chan bool, 1),
|
||||
cancel: cancel,
|
||||
ctx: ctx,
|
||||
dialFn: func(ctx context.Context, earlyData []byte) (net.Conn, error) {
|
||||
return dialer.DialContext(ctx, network, address, earlyData)
|
||||
},
|
||||
}, nil
|
||||
}
|
Loading…
Reference in a new issue