Merge pull request #1258 from apernet/wip-server-fastopen

feat(server): tcp fast open on direct outbounds
This commit is contained in:
Toby 2024-12-10 23:03:52 -08:00 committed by GitHub
commit 6655d2a78d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 364 additions and 75 deletions

View file

@ -204,6 +204,7 @@ type serverConfigOutboundDirect struct {
BindIPv4 string `mapstructure:"bindIPv4"`
BindIPv6 string `mapstructure:"bindIPv6"`
BindDevice string `mapstructure:"bindDevice"`
FastOpen bool `mapstructure:"fastOpen"`
}
type serverConfigOutboundSOCKS5 struct {
@ -518,18 +519,18 @@ func (c *serverConfig) fillQUICConfig(hyConfig *server.Config) error {
}
func serverConfigOutboundDirectToOutbound(c serverConfigOutboundDirect) (outbounds.PluggableOutbound, error) {
var mode outbounds.DirectOutboundMode
opts := outbounds.DirectOutboundOptions{}
switch strings.ToLower(c.Mode) {
case "", "auto":
mode = outbounds.DirectOutboundModeAuto
opts.Mode = outbounds.DirectOutboundModeAuto
case "64":
mode = outbounds.DirectOutboundMode64
opts.Mode = outbounds.DirectOutboundMode64
case "46":
mode = outbounds.DirectOutboundMode46
opts.Mode = outbounds.DirectOutboundMode46
case "6":
mode = outbounds.DirectOutboundMode6
opts.Mode = outbounds.DirectOutboundMode6
case "4":
mode = outbounds.DirectOutboundMode4
opts.Mode = outbounds.DirectOutboundMode4
default:
return nil, configError{Field: "outbounds.direct.mode", Err: errors.New("unsupported mode")}
}
@ -546,12 +547,14 @@ func serverConfigOutboundDirectToOutbound(c serverConfigOutboundDirect) (outboun
if len(c.BindIPv6) > 0 && ip6 == nil {
return nil, configError{Field: "outbounds.direct.bindIPv6", Err: errors.New("invalid IPv6 address")}
}
return outbounds.NewDirectOutboundBindToIPs(mode, ip4, ip6)
opts.BindIP4 = ip4
opts.BindIP6 = ip6
}
if bindDevice {
return outbounds.NewDirectOutboundBindToDevice(mode, c.BindDevice)
opts.DeviceName = c.BindDevice
}
return outbounds.NewDirectOutboundSimple(mode), nil
opts.FastOpen = c.FastOpen
return outbounds.NewDirectOutboundWithOptions(opts)
}
func serverConfigOutboundSOCKS5ToOutbound(c serverConfigOutboundSOCKS5) (outbounds.PluggableOutbound, error) {

View file

@ -138,6 +138,7 @@ func TestServerConfig(t *testing.T) {
BindIPv4: "2.4.6.8",
BindIPv6: "0:0:0:0:0:ffff:0204:0608",
BindDevice: "eth233",
FastOpen: true,
},
},
{

View file

@ -108,6 +108,7 @@ outbounds:
bindIPv4: 2.4.6.8
bindIPv6: 0:0:0:0:0:ffff:0204:0608
bindDevice: eth233
fastOpen: true
- name: badstuff
type: socks5
socks5:

View file

@ -25,7 +25,7 @@ require (
github.com/txthinking/socks5 v0.0.0-20230325130024-4230056ae301
go.uber.org/zap v1.24.0
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
golang.org/x/sys v0.23.0
golang.org/x/sys v0.25.0
)
require (
@ -33,6 +33,8 @@ require (
github.com/apernet/quic-go v0.48.2-0.20241104191913-cb103fcecfe7 // indirect
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6 // indirect
github.com/cloudflare/circl v1.3.9 // indirect
github.com/database64128/netx-go v0.0.0-20240905055117-62795b8b054a // indirect
github.com/database64128/tfo-go/v2 v2.2.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect

View file

@ -63,6 +63,10 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/database64128/netx-go v0.0.0-20240905055117-62795b8b054a h1:t4SDi0pmNkryzKdM4QF3o5vqSP4GRjeZD/6j3nyxNP0=
github.com/database64128/netx-go v0.0.0-20240905055117-62795b8b054a/go.mod h1:7K2NQKbabB5mBl41vF6YayYl5g7YpDwc4dQ5iMpP3Lg=
github.com/database64128/tfo-go/v2 v2.2.2 h1:BxynF4qGF5ct3DpPLEG62uyJZ3LQhqaf0Ken+kyy7PM=
github.com/database64128/tfo-go/v2 v2.2.2/go.mod h1:2IW8jppdBwdVMjA08uEyMNnqiAHKUlqAA+J8NrsfktY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -463,8 +467,8 @@ golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=

View file

@ -27,7 +27,7 @@ require (
golang.org/x/crypto v0.26.0 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/net v0.28.0 // indirect
golang.org/x/sys v0.23.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/text v0.17.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
google.golang.org/protobuf v1.34.1 // indirect

View file

@ -58,8 +58,7 @@ golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=

View file

@ -8,6 +8,7 @@ require (
github.com/apernet/hysteria/core/v2 v2.0.0-00010101000000-000000000000
github.com/apernet/quic-go v0.48.2-0.20241104191913-cb103fcecfe7
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6
github.com/database64128/tfo-go/v2 v2.2.2
github.com/hashicorp/golang-lru/v2 v2.0.5
github.com/miekg/dns v1.1.59
github.com/refraction-networking/utls v1.6.6
@ -21,6 +22,7 @@ require (
require (
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/cloudflare/circl v1.3.9 // indirect
github.com/database64128/netx-go v0.0.0-20240905055117-62795b8b054a // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
@ -36,7 +38,7 @@ require (
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.23.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/text v0.17.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect

View file

@ -10,6 +10,10 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
github.com/cloudflare/circl v1.3.9 h1:QFrlgFYf2Qpi8bSpVPK1HBvWpx16v/1TZivyo7pGuBE=
github.com/cloudflare/circl v1.3.9/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/database64128/netx-go v0.0.0-20240905055117-62795b8b054a h1:t4SDi0pmNkryzKdM4QF3o5vqSP4GRjeZD/6j3nyxNP0=
github.com/database64128/netx-go v0.0.0-20240905055117-62795b8b054a/go.mod h1:7K2NQKbabB5mBl41vF6YayYl5g7YpDwc4dQ5iMpP3Lg=
github.com/database64128/tfo-go/v2 v2.2.2 h1:BxynF4qGF5ct3DpPLEG62uyJZ3LQhqaf0Ken+kyy7PM=
github.com/database64128/tfo-go/v2 v2.2.2/go.mod h1:2IW8jppdBwdVMjA08uEyMNnqiAHKUlqAA+J8NrsfktY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -92,8 +96,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=

View file

@ -0,0 +1,229 @@
package outbounds
import (
"net"
"sync"
"time"
"github.com/database64128/tfo-go/v2"
)
type fastOpenDialer struct {
dialer *tfo.Dialer
}
func newFastOpenDialer(netDialer *net.Dialer) *fastOpenDialer {
return &fastOpenDialer{
dialer: &tfo.Dialer{
Dialer: *netDialer,
},
}
}
// Dial returns immediately without actually establishing a connection.
// The connection will be established by the first Write() call.
func (d *fastOpenDialer) Dial(network, address string) (net.Conn, error) {
return &fastOpenConn{
dialer: d.dialer,
network: network,
address: address,
readyChan: make(chan struct{}),
}, nil
}
type fastOpenConn struct {
dialer *tfo.Dialer
network string
address string
conn net.Conn
connLock sync.RWMutex
readyChan chan struct{}
// States before connection ready
deadline *time.Time
readDeadline *time.Time
writeDeadline *time.Time
}
func (c *fastOpenConn) Read(b []byte) (n int, err error) {
c.connLock.RLock()
conn := c.conn
c.connLock.RUnlock()
if conn != nil {
return conn.Read(b)
}
// Wait until the connection is ready or closed
<-c.readyChan
if c.conn == nil {
// This is equivalent to isClosedBeforeReady() == true
return 0, net.ErrClosed
}
return c.conn.Read(b)
}
func (c *fastOpenConn) Write(b []byte) (n int, err error) {
c.connLock.RLock()
conn := c.conn
c.connLock.RUnlock()
if conn != nil {
return conn.Write(b)
}
c.connLock.RLock()
closed := c.isClosedBeforeReady()
c.connLock.RUnlock()
if closed {
return 0, net.ErrClosed
}
c.connLock.Lock()
defer c.connLock.Unlock()
if c.isClosedBeforeReady() {
// Closed by other goroutine
return 0, net.ErrClosed
}
conn = c.conn
if conn != nil {
// Established by other goroutine
return conn.Write(b)
}
conn, err = c.dialer.Dial(c.network, c.address, b)
if err != nil {
close(c.readyChan)
return 0, err
}
// Apply pre-set states
if c.deadline != nil {
_ = conn.SetDeadline(*c.deadline)
}
if c.readDeadline != nil {
_ = conn.SetReadDeadline(*c.readDeadline)
}
if c.writeDeadline != nil {
_ = conn.SetWriteDeadline(*c.writeDeadline)
}
c.conn = conn
close(c.readyChan)
return len(b), nil
}
func (c *fastOpenConn) Close() error {
c.connLock.RLock()
defer c.connLock.RUnlock()
if c.isClosedBeforeReady() {
return net.ErrClosed
}
if c.conn != nil {
return c.conn.Close()
}
close(c.readyChan)
return nil
}
// isClosedBeforeReady returns true if the connection is closed before the real connection is established.
// This function should be called with connLock.RLock().
func (c *fastOpenConn) isClosedBeforeReady() bool {
select {
case <-c.readyChan:
if c.conn == nil {
return true
}
default:
}
return false
}
func (c *fastOpenConn) LocalAddr() net.Addr {
c.connLock.RLock()
defer c.connLock.RUnlock()
if c.conn != nil {
return c.conn.LocalAddr()
}
return nil
}
func (c *fastOpenConn) RemoteAddr() net.Addr {
c.connLock.RLock()
conn := c.conn
c.connLock.RUnlock()
if conn != nil {
return conn.RemoteAddr()
}
addr, err := net.ResolveTCPAddr(c.network, c.address)
if err != nil {
return nil
}
return addr
}
func (c *fastOpenConn) SetDeadline(t time.Time) error {
c.connLock.RLock()
defer c.connLock.RUnlock()
c.deadline = &t
if c.conn != nil {
return c.conn.SetDeadline(t)
}
if c.isClosedBeforeReady() {
return net.ErrClosed
}
return nil
}
func (c *fastOpenConn) SetReadDeadline(t time.Time) error {
c.connLock.RLock()
defer c.connLock.RUnlock()
c.readDeadline = &t
if c.conn != nil {
return c.conn.SetReadDeadline(t)
}
if c.isClosedBeforeReady() {
return net.ErrClosed
}
return nil
}
func (c *fastOpenConn) SetWriteDeadline(t time.Time) error {
c.connLock.RLock()
defer c.connLock.RUnlock()
c.writeDeadline = &t
if c.conn != nil {
return c.conn.SetWriteDeadline(t)
}
if c.isClosedBeforeReady() {
return net.ErrClosed
}
return nil
}
var _ net.Conn = (*fastOpenConn)(nil)

View file

@ -35,8 +35,8 @@ type directOutbound struct {
Mode DirectOutboundMode
// Dialer4 and Dialer6 are used for IPv4 and IPv6 TCP connections respectively.
Dialer4 *net.Dialer
Dialer6 *net.Dialer
DialFunc4 func(network, address string) (net.Conn, error)
DialFunc6 func(network, address string) (net.Conn, error)
// DeviceName & BindIPs are for UDP connections. They don't use dialers, so we
// need to bind them when creating the connection.
@ -45,6 +45,16 @@ type directOutbound struct {
BindIP6 net.IP
}
type DirectOutboundOptions struct {
Mode DirectOutboundMode
DeviceName string
BindIP4 net.IP
BindIP6 net.IP
FastOpen bool
}
type noAddressError struct {
IPv4 bool
IPv6 bool
@ -84,6 +94,57 @@ func (e resolveError) Unwrap() error {
return e.Err
}
func NewDirectOutboundWithOptions(opts DirectOutboundOptions) (PluggableOutbound, error) {
dialer4 := &net.Dialer{
Timeout: defaultDialerTimeout,
}
if opts.BindIP4 != nil {
if opts.BindIP4.To4() == nil {
return nil, errors.New("BindIP4 must be an IPv4 address")
}
dialer4.LocalAddr = &net.TCPAddr{
IP: opts.BindIP4,
}
}
dialer6 := &net.Dialer{
Timeout: defaultDialerTimeout,
}
if opts.BindIP6 != nil {
if opts.BindIP6.To4() != nil {
return nil, errors.New("BindIP6 must be an IPv6 address")
}
dialer6.LocalAddr = &net.TCPAddr{
IP: opts.BindIP6,
}
}
if opts.DeviceName != "" {
err := dialerBindToDevice(dialer4, opts.DeviceName)
if err != nil {
return nil, err
}
err = dialerBindToDevice(dialer6, opts.DeviceName)
if err != nil {
return nil, err
}
}
dialFunc4 := dialer4.Dial
dialFunc6 := dialer6.Dial
if opts.FastOpen {
dialFunc4 = newFastOpenDialer(dialer4).Dial
dialFunc6 = newFastOpenDialer(dialer6).Dial
}
return &directOutbound{
Mode: opts.Mode,
DialFunc4: dialFunc4,
DialFunc6: dialFunc6,
DeviceName: opts.DeviceName,
BindIP4: opts.BindIP4,
BindIP6: opts.BindIP6,
}, nil
}
// NewDirectOutboundSimple creates a new directOutbound with the given mode,
// without binding to a specific device. Works on all platforms.
func NewDirectOutboundSimple(mode DirectOutboundMode) PluggableOutbound {
@ -91,9 +152,9 @@ func NewDirectOutboundSimple(mode DirectOutboundMode) PluggableOutbound {
Timeout: defaultDialerTimeout,
}
return &directOutbound{
Mode: mode,
Dialer4: d,
Dialer6: d,
Mode: mode,
DialFunc4: d.Dial,
DialFunc6: d.Dial,
}
}
@ -102,34 +163,20 @@ func NewDirectOutboundSimple(mode DirectOutboundMode) PluggableOutbound {
// can be nil, in which case the directOutbound will not bind to a specific address
// for that family.
func NewDirectOutboundBindToIPs(mode DirectOutboundMode, bindIP4, bindIP6 net.IP) (PluggableOutbound, error) {
if bindIP4 != nil && bindIP4.To4() == nil {
return nil, errors.New("bindIP4 must be an IPv4 address")
}
if bindIP6 != nil && bindIP6.To4() != nil {
return nil, errors.New("bindIP6 must be an IPv6 address")
}
ob := &directOutbound{
Mode: mode,
Dialer4: &net.Dialer{
Timeout: defaultDialerTimeout,
},
Dialer6: &net.Dialer{
Timeout: defaultDialerTimeout,
},
return NewDirectOutboundWithOptions(DirectOutboundOptions{
Mode: mode,
BindIP4: bindIP4,
BindIP6: bindIP6,
}
if bindIP4 != nil {
ob.Dialer4.LocalAddr = &net.TCPAddr{
IP: bindIP4,
}
}
if bindIP6 != nil {
ob.Dialer6.LocalAddr = &net.TCPAddr{
IP: bindIP6,
}
}
return ob, nil
})
}
// NewDirectOutboundBindToDevice creates a new directOutbound with the given mode,
// and binds to the given device. Only works on Linux.
func NewDirectOutboundBindToDevice(mode DirectOutboundMode, deviceName string) (PluggableOutbound, error) {
return NewDirectOutboundWithOptions(DirectOutboundOptions{
Mode: mode,
DeviceName: deviceName,
})
}
// resolve is our built-in DNS resolver for handling the case when
@ -201,9 +248,9 @@ func (d *directOutbound) TCP(reqAddr *AddrEx) (net.Conn, error) {
func (d *directOutbound) dialTCP(ip net.IP, port uint16) (net.Conn, error) {
if ip.To4() != nil {
return d.Dialer4.Dial("tcp4", net.JoinHostPort(ip.String(), strconv.Itoa(int(port))))
return d.DialFunc4("tcp4", net.JoinHostPort(ip.String(), strconv.Itoa(int(port))))
} else {
return d.Dialer6.Dial("tcp6", net.JoinHostPort(ip.String(), strconv.Itoa(int(port))))
return d.DialFunc6("tcp6", net.JoinHostPort(ip.String(), strconv.Itoa(int(port))))
}
}

View file

@ -6,31 +6,31 @@ import (
"syscall"
)
// NewDirectOutboundBindToDevice creates a new directOutbound with the given mode,
// and binds to the given device. Only works on Linux.
func NewDirectOutboundBindToDevice(mode DirectOutboundMode, deviceName string) (PluggableOutbound, error) {
func dialerBindToDevice(dialer *net.Dialer, deviceName string) error {
if err := verifyDeviceName(deviceName); err != nil {
return nil, err
return err
}
d := &net.Dialer{
Timeout: defaultDialerTimeout,
Control: func(network, address string, c syscall.RawConn) error {
var errBind error
err := c.Control(func(fd uintptr) {
errBind = syscall.BindToDevice(int(fd), deviceName)
})
originControl := dialer.Control
dialer.Control = func(network, address string, c syscall.RawConn) error {
if originControl != nil {
// Chaining other control function
err := originControl(network, address, c)
if err != nil {
return err
}
return errBind
},
}
var errBind error
err := c.Control(func(fd uintptr) {
errBind = syscall.BindToDevice(int(fd), deviceName)
})
if err != nil {
return err
}
return errBind
}
return &directOutbound{
Mode: mode,
Dialer4: d,
Dialer6: d,
DeviceName: deviceName,
}, nil
return nil
}
func verifyDeviceName(deviceName string) error {

View file

@ -7,11 +7,8 @@ import (
"net"
)
// NewDirectOutboundBindToDevice creates a new directOutbound with the given mode,
// and binds to the given device. This doesn't work on non-Linux platforms, so this
// is just a stub function that always returns an error.
func NewDirectOutboundBindToDevice(mode DirectOutboundMode, deviceName string) (PluggableOutbound, error) {
return nil, errors.New("binding to device is not supported on this platform")
func dialerBindToDevice(dialer *net.Dialer, deviceName string) error {
return errors.New("binding to device is not supported on this platform")
}
func udpConnBindToDevice(conn *net.UDPConn, deviceName string) error {