mirror of
https://github.com/apernet/hysteria.git
synced 2025-04-02 03:57:38 +03:00
feat(server): tcp fast open on direct outbounds
This commit is contained in:
parent
15e31d48a0
commit
16c964b3e1
11 changed files with 363 additions and 72 deletions
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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=
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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=
|
||||
|
|
230
extras/outbounds/fastopen.go
Normal file
230
extras/outbounds/fastopen.go
Normal file
|
@ -0,0 +1,230 @@
|
|||
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,
|
||||
Fallback: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
|
@ -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))))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue