Merge remote-tracking branch 'meta/Alpha' into Alpha

This commit is contained in:
Skyxim 2022-04-28 08:56:00 +08:00
commit a38b2bcb6d
17 changed files with 298 additions and 147 deletions

View file

@ -1,7 +1,7 @@
name: Alpha name: alpha
on: [push] on: [push]
jobs: jobs:
Feature-build: Build:
if: ${{ !contains(github.event.head_commit.message, '[Skip CI]') }} if: ${{ !contains(github.event.head_commit.message, '[Skip CI]') }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -57,15 +57,15 @@ jobs:
files: bin/* files: bin/*
prerelease: true prerelease: true
- name: send telegram message on push # - name: send telegram message on push
uses: appleboy/telegram-action@master # uses: appleboy/telegram-action@master
with: # with:
to: ${{ secrets.TTELEGRAM_CHAT_ID }} # to: ${{ secrets.TTELEGRAM_CHAT_ID }}
token: ${{ secrets.TELEGRAM_TOKEN }} # token: ${{ secrets.TELEGRAM_TOKEN }}
message: | # message: |
${{ github.actor }} created commit: # ${{ github.actor }} created commit:
Commit message: ${{ github.event.commits[0].message }} # Commit message: ${{ github.event.commits[0].message }}
#
Repository: ${{ github.repository }} # Repository: ${{ github.repository }}
#
See changes: https://github.com/${{ github.repository }}/commit/${{github.sha}} # See changes: https://github.com/${{ github.repository }}/commit/${{github.sha}}

View file

@ -2,9 +2,12 @@ package outbound
import ( import (
"context" "context"
"crypto/sha1"
"encoding/hex"
"encoding/json" "encoding/json"
"errors" "errors"
"net" "net"
"regexp"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
@ -136,3 +139,28 @@ func (c *packetConn) AppendToChains(a C.ProxyAdapter) {
func newPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn { func newPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn {
return &packetConn{pc, []string{a.Name()}} return &packetConn{pc, []string{a.Name()}}
} }
func uuidMap(str string) string {
match, _ := regexp.MatchString(`[\da-f]{8}(-[\da-f]{4}){3}-[\da-f]{12}$`, str)
if !match {
var Nil [16]byte
h := sha1.New()
h.Write(Nil[:])
h.Write([]byte(str))
u := h.Sum(nil)[:16]
u[6] = (u[6] & 0x0f) | (5 << 4)
u[8] = u[8]&(0xff>>2) | (0x02 << 6)
buf := make([]byte, 36)
hex.Encode(buf[0:8], u[0:4])
buf[8] = '-'
hex.Encode(buf[9:13], u[4:6])
buf[13] = '-'
hex.Encode(buf[14:18], u[6:8])
buf[18] = '-'
hex.Encode(buf[19:23], u[8:10])
buf[23] = '-'
hex.Encode(buf[24:], u[10:])
return string(buf)
}
return str
}

View file

@ -386,7 +386,7 @@ func NewVless(option VlessOption) (*Vless, error) {
} }
} }
client, err := vless.NewClient(option.UUID, addons, option.FlowShow) client, err := vless.NewClient(uuidMap(option.UUID), addons, option.FlowShow)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -266,7 +266,7 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
func NewVmess(option VmessOption) (*Vmess, error) { func NewVmess(option VmessOption) (*Vmess, error) {
security := strings.ToLower(option.Cipher) security := strings.ToLower(option.Cipher)
client, err := vmess.NewClient(vmess.Config{ client, err := vmess.NewClient(vmess.Config{
UUID: option.UUID, UUID: uuidMap(option.UUID),
AlterID: uint16(option.AlterID), AlterID: uint16(option.AlterID),
Security: security, Security: security,
HostName: option.Server, HostName: option.Server,

30
common/net/relay.go Normal file
View file

@ -0,0 +1,30 @@
package net
import (
"io"
"net"
"time"
"github.com/Dreamacro/clash/common/pool"
)
// Relay copies between left and right bidirectionally.
func Relay(leftConn, rightConn net.Conn) {
ch := make(chan error)
go func() {
buf := pool.Get(pool.RelayBufferSize)
// Wrapping to avoid using *net.TCPConn.(ReadFrom)
// See also https://github.com/Dreamacro/clash/pull/1209
_, err := io.CopyBuffer(WriteOnlyWriter{Writer: leftConn}, ReadOnlyReader{Reader: rightConn}, buf)
pool.Put(buf)
leftConn.SetReadDeadline(time.Now())
ch <- err
}()
buf := pool.Get(pool.RelayBufferSize)
io.CopyBuffer(WriteOnlyWriter{Writer: rightConn}, ReadOnlyReader{Reader: leftConn}, buf)
pool.Put(buf)
rightConn.SetReadDeadline(time.Now())
<-ch
}

View file

@ -31,7 +31,7 @@ func NewDecoder(option Option) *Decoder {
// Decode transform a map[string]any to a struct // Decode transform a map[string]any to a struct
func (d *Decoder) Decode(src map[string]any, dst any) error { func (d *Decoder) Decode(src map[string]any, dst any) error {
if reflect.TypeOf(dst).Kind() != reflect.Ptr { if reflect.TypeOf(dst).Kind() != reflect.Ptr {
return fmt.Errorf("Decode must recive a ptr struct") return fmt.Errorf("decode must recive a ptr struct")
} }
t := reflect.TypeOf(dst).Elem() t := reflect.TypeOf(dst).Elem()
v := reflect.ValueOf(dst).Elem() v := reflect.ValueOf(dst).Elem()
@ -291,7 +291,7 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
field reflect.StructField field reflect.StructField
val reflect.Value val reflect.Value
} }
fields := []field{} var fields []field
for len(structs) > 0 { for len(structs) > 0 {
structVal := structs[0] structVal := structs[0]
structs = structs[1:] structs = structs[1:]

View file

@ -4,14 +4,19 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"github.com/Dreamacro/clash/log"
"net" "net"
"net/netip" "net/netip"
"sync"
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
) )
var DisableIPv6 = false var (
dialMux sync.Mutex
actualSingleDialContext = singleDialContext
actualDualStackDialContext = dualStackDialContext
DisableIPv6 = false
)
func DialContext(ctx context.Context, network, address string, options ...Option) (net.Conn, error) { func DialContext(ctx context.Context, network, address string, options ...Option) (net.Conn, error) {
opt := &option{ opt := &option{
@ -29,37 +34,9 @@ func DialContext(ctx context.Context, network, address string, options ...Option
switch network { switch network {
case "tcp4", "tcp6", "udp4", "udp6": case "tcp4", "tcp6", "udp4", "udp6":
host, port, err := net.SplitHostPort(address) return actualSingleDialContext(ctx, network, address, opt)
if err != nil {
return nil, err
}
var ip netip.Addr
switch network {
case "tcp4", "udp4":
if !opt.direct {
ip, err = resolver.ResolveIPv4ProxyServerHost(host)
} else {
ip, err = resolver.ResolveIPv4(host)
}
default:
if !opt.direct {
ip, err = resolver.ResolveIPv6ProxyServerHost(host)
} else {
ip, err = resolver.ResolveIPv6(host)
}
}
if err != nil {
return nil, err
}
return dialContext(ctx, network, ip, port, opt)
case "tcp", "udp": case "tcp", "udp":
if TCPConcurrent { return actualDualStackDialContext(ctx, network, address, opt)
return concurrentDialContext(ctx, network, address, opt)
} else {
return dualStackDialContext(ctx, network, address, opt)
}
default: default:
return nil, errors.New("network invalid") return nil, errors.New("network invalid")
} }
@ -97,6 +74,19 @@ func ListenPacket(ctx context.Context, network, address string, options ...Optio
return lc.ListenPacket(ctx, network, address) return lc.ListenPacket(ctx, network, address)
} }
func SetDial(concurrent bool) {
dialMux.Lock()
if concurrent {
actualSingleDialContext = concurrentSingleDialContext
actualDualStackDialContext = concurrentDualStackDialContext
} else {
actualSingleDialContext = singleDialContext
actualDualStackDialContext = concurrentDualStackDialContext
}
dialMux.Unlock()
}
func dialContext(ctx context.Context, network string, destination netip.Addr, port string, opt *option) (net.Conn, error) { func dialContext(ctx context.Context, network string, destination netip.Addr, port string, opt *option) (net.Conn, error) {
dialer := &net.Dialer{} dialer := &net.Dialer{}
if opt.interfaceName != "" { if opt.interfaceName != "" {
@ -196,12 +186,24 @@ func dualStackDialContext(ctx context.Context, network, address string, opt *opt
return nil, errors.New("never touched") return nil, errors.New("never touched")
} }
func concurrentDialContext(ctx context.Context, network, address string, opt *option) (net.Conn, error) { func concurrentDualStackDialContext(ctx context.Context, network, address string, opt *option) (net.Conn, error) {
host, port, err := net.SplitHostPort(address) host, port, err := net.SplitHostPort(address)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var ips []netip.Addr
if opt.direct {
ips, err = resolver.ResolveAllIP(host)
} else {
ips, err = resolver.ResolveAllIPProxyServerHost(host)
}
return concurrentDialContext(ctx, network, ips, port, opt)
}
func concurrentDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
returned := make(chan struct{}) returned := make(chan struct{})
defer close(returned) defer close(returned)
@ -213,13 +215,6 @@ func concurrentDialContext(ctx context.Context, network, address string, opt *op
} }
results := make(chan dialResult) results := make(chan dialResult)
var ips []netip.Addr
if opt.direct {
ips, err = resolver.ResolveAllIP(host)
} else {
ips, err = resolver.ResolveAllIPProxyServerHost(host)
}
tcpRacer := func(ctx context.Context, ip netip.Addr) { tcpRacer := func(ctx context.Context, ip netip.Addr) {
result := dialResult{ip: ip} result := dialResult{ip: ip}
@ -239,7 +234,6 @@ func concurrentDialContext(ctx context.Context, network, address string, opt *op
v = "6" v = "6"
} }
log.Debugln("[%s] try use [%s] connected", host, ip.String())
result.Conn, result.error = dialContext(ctx, network+v, ip, port, opt) result.Conn, result.error = dialContext(ctx, network+v, ip, port, opt)
} }
@ -251,16 +245,70 @@ func concurrentDialContext(ctx context.Context, network, address string, opt *op
for res := range results { for res := range results {
connCount-- connCount--
if res.error == nil { if res.error == nil {
log.Debugln("[%s] used [%s] connected", host, res.ip.String())
return res.Conn, nil return res.Conn, nil
} }
log.Errorln("connect error:%v", res.error)
if connCount == 0 { if connCount == 0 {
log.Errorln("connect [%s] all ip failed", host)
break break
} }
} }
return nil, errors.New("all ip tcp shakeHands failed") return nil, errors.New("all ip tcp shake hands failed")
}
func singleDialContext(ctx context.Context, network string, address string, opt *option) (net.Conn, error) {
host, port, err := net.SplitHostPort(address)
if err != nil {
return nil, err
}
var ip netip.Addr
switch network {
case "tcp4", "udp4":
if !opt.direct {
ip, err = resolver.ResolveIPv4ProxyServerHost(host)
} else {
ip, err = resolver.ResolveIPv4(host)
}
default:
if !opt.direct {
ip, err = resolver.ResolveIPv6ProxyServerHost(host)
} else {
ip, err = resolver.ResolveIPv6(host)
}
}
if err != nil {
return nil, err
}
return dialContext(ctx, network, ip, port, opt)
}
func concurrentSingleDialContext(ctx context.Context, network string, address string, opt *option) (net.Conn, error) {
host, port, err := net.SplitHostPort(address)
if err != nil {
return nil, err
}
var ips []netip.Addr
switch network {
case "tcp4", "udp4":
if !opt.direct {
ips, err = resolver.ResolveAllIPv4ProxyServerHost(host)
} else {
ips, err = resolver.ResolveAllIPv4(host)
}
default:
if !opt.direct {
ips, err = resolver.ResolveAllIPv6ProxyServerHost(host)
} else {
ips, err = resolver.ResolveAllIPv6(host)
}
}
if err != nil {
return nil, err
}
return concurrentDialContext(ctx, network, ips, port, opt)
} }

View file

@ -6,7 +6,6 @@ var (
DefaultOptions []Option DefaultOptions []Option
DefaultInterface = atomic.NewString("") DefaultInterface = atomic.NewString("")
DefaultRoutingMark = atomic.NewInt32(0) DefaultRoutingMark = atomic.NewInt32(0)
TCPConcurrent = false
) )
type option struct { type option struct {

View file

@ -23,7 +23,8 @@ var (
var Dispatcher SnifferDispatcher var Dispatcher SnifferDispatcher
type SnifferDispatcher struct { type (
SnifferDispatcher struct {
enable bool enable bool
sniffers []C.Sniffer sniffers []C.Sniffer
@ -32,6 +33,7 @@ type SnifferDispatcher struct {
skipSNI *trie.DomainTrie[bool] skipSNI *trie.DomainTrie[bool]
portRanges *[]utils.Range[uint16] portRanges *[]utils.Range[uint16]
} }
)
func (sd *SnifferDispatcher) TCPSniff(conn net.Conn, metadata *C.Metadata) { func (sd *SnifferDispatcher) TCPSniff(conn net.Conn, metadata *C.Metadata) {
bufConn, ok := conn.(*CN.BufferedConn) bufConn, ok := conn.(*CN.BufferedConn)
@ -101,7 +103,6 @@ func (sd *SnifferDispatcher) sniffDomain(conn *CN.BufferedConn, metadata *C.Meta
log.Errorln("[Sniffer] [%s] Maybe read timeout, Consider adding skip", metadata.DstIP.String()) log.Errorln("[Sniffer] [%s] Maybe read timeout, Consider adding skip", metadata.DstIP.String())
conn.Close() conn.Close()
} }
log.Errorln("[Sniffer] %v", err) log.Errorln("[Sniffer] %v", err)
return "", err return "", err
} }

View file

@ -254,8 +254,8 @@ func updateGeneral(general *config.General, force bool) {
resolver.DisableIPv6 = true resolver.DisableIPv6 = true
} }
dialer.TCPConcurrent = general.TCPConcurrent if general.TCPConcurrent {
if dialer.TCPConcurrent { dialer.SetDial(general.TCPConcurrent)
log.Infoln("Use tcp concurrent") log.Infoln("Use tcp concurrent")
} }

View file

@ -19,12 +19,7 @@ func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.Cache[string,
client := newClient(c.RemoteAddr(), in) client := newClient(c.RemoteAddr(), in)
defer client.CloseIdleConnections() defer client.CloseIdleConnections()
var conn *N.BufferedConn conn := N.NewBufferedConn(c)
if bufConn, ok := c.(*N.BufferedConn); ok {
conn = bufConn
} else {
conn = N.NewBufferedConn(c)
}
keepAlive := true keepAlive := true
trusted := cache == nil // disable authenticate if cache is nil trusted := cache == nil // disable authenticate if cache is nil
@ -66,6 +61,12 @@ func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.Cache[string,
request.RequestURI = "" request.RequestURI = ""
if isUpgradeRequest(request) {
if resp = handleUpgrade(conn, conn.RemoteAddr(), request, in); resp == nil {
return // hijack connection
}
}
removeHopByHopHeaders(request.Header) removeHopByHopHeaders(request.Header)
removeExtraHTTPHostPort(request) removeExtraHTTPHostPort(request)
@ -95,7 +96,7 @@ func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.Cache[string,
} }
} }
conn.Close() _ = conn.Close()
} }
func authenticate(request *http.Request, cache *cache.Cache[string, bool]) *http.Response { func authenticate(request *http.Request, cache *cache.Cache[string, bool]) *http.Response {

95
listener/http/upgrade.go Normal file
View file

@ -0,0 +1,95 @@
package http
import (
"context"
"crypto/tls"
"net"
"net/http"
"strings"
"time"
"github.com/Dreamacro/clash/adapter/inbound"
N "github.com/Dreamacro/clash/common/net"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/socks5"
)
func isUpgradeRequest(req *http.Request) bool {
return strings.EqualFold(req.Header.Get("Connection"), "Upgrade")
}
func handleUpgrade(localConn net.Conn, source net.Addr, request *http.Request, in chan<- C.ConnContext) (resp *http.Response) {
removeProxyHeaders(request.Header)
removeExtraHTTPHostPort(request)
address := request.Host
if _, _, err := net.SplitHostPort(address); err != nil {
port := "80"
if request.TLS != nil {
port = "443"
}
address = net.JoinHostPort(address, port)
}
dstAddr := socks5.ParseAddr(address)
if dstAddr == nil {
return
}
left, right := net.Pipe()
in <- inbound.NewHTTP(dstAddr, source, right)
var remoteServer *N.BufferedConn
if request.TLS != nil {
tlsConn := tls.Client(left, &tls.Config{
ServerName: request.URL.Hostname(),
})
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
defer cancel()
if tlsConn.HandshakeContext(ctx) != nil {
_ = localConn.Close()
_ = left.Close()
return
}
remoteServer = N.NewBufferedConn(tlsConn)
} else {
remoteServer = N.NewBufferedConn(left)
}
defer func() {
_ = remoteServer.Close()
}()
err := request.Write(remoteServer)
if err != nil {
_ = localConn.Close()
return
}
resp, err = http.ReadResponse(remoteServer.Reader(), request)
if err != nil {
_ = localConn.Close()
return
}
if resp.StatusCode == http.StatusSwitchingProtocols {
removeProxyHeaders(resp.Header)
err = localConn.SetReadDeadline(time.Time{}) // set to not time out
if err != nil {
return
}
err = resp.Write(localConn)
if err != nil {
return
}
N.Relay(remoteServer, localConn) // blocking here
_ = localConn.Close()
resp = nil
}
return
}

View file

@ -8,15 +8,21 @@ import (
"strings" "strings"
) )
// removeHopByHopHeaders remove Proxy-* headers
func removeProxyHeaders(header http.Header) {
header.Del("Proxy-Connection")
header.Del("Proxy-Authenticate")
header.Del("Proxy-Authorization")
}
// removeHopByHopHeaders remove hop-by-hop header // removeHopByHopHeaders remove hop-by-hop header
func removeHopByHopHeaders(header http.Header) { func removeHopByHopHeaders(header http.Header) {
// Strip hop-by-hop header based on RFC: // Strip hop-by-hop header based on RFC:
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1 // http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1
// https://www.mnot.net/blog/2011/07/11/what_proxies_must_do // https://www.mnot.net/blog/2011/07/11/what_proxies_must_do
header.Del("Proxy-Connection") removeProxyHeaders(header)
header.Del("Proxy-Authenticate")
header.Del("Proxy-Authorization")
header.Del("TE") header.Del("TE")
header.Del("Trailers") header.Del("Trailers")
header.Del("Transfer-Encoding") header.Del("Transfer-Encoding")

View file

@ -1,6 +1,7 @@
package nat package nat
import ( import (
log "github.com/sirupsen/logrus"
"io" "io"
"net" "net"
"net/netip" "net/netip"
@ -43,7 +44,8 @@ func Start(device io.ReadWriter, gateway, portal, broadcast netip.Addr) (*TCP, *
for { for {
n, err := device.Read(buf) n, err := device.Read(buf)
if err != nil { if err != nil {
return log.Warnln("system error:%s", err.Error())
continue
} }
raw := buf[:n] raw := buf[:n]

View file

@ -2,7 +2,6 @@ package nat
import ( import (
"net/netip" "net/netip"
"sync"
"github.com/Dreamacro/clash/common/generics/list" "github.com/Dreamacro/clash/common/generics/list"
) )
@ -25,7 +24,6 @@ type binding struct {
} }
type table struct { type table struct {
mu sync.Mutex
tuples map[tuple]*list.Element[*binding] tuples map[tuple]*list.Element[*binding]
ports [portLength]*list.Element[*binding] ports [portLength]*list.Element[*binding]
available *list.List[*binding] available *list.List[*binding]
@ -39,13 +37,13 @@ func (t *table) tupleOf(port uint16) tuple {
elm := t.ports[offset] elm := t.ports[offset]
t.available.MoveToFront(elm)
return elm.Value.tuple return elm.Value.tuple
} }
func (t *table) portOf(tuple tuple) uint16 { func (t *table) portOf(tuple tuple) uint16 {
t.mu.Lock()
elm := t.tuples[tuple] elm := t.tuples[tuple]
t.mu.Unlock()
if elm == nil { if elm == nil {
return 0 return 0
} }
@ -59,11 +57,8 @@ func (t *table) newConn(tuple tuple) uint16 {
elm := t.available.Back() elm := t.available.Back()
b := elm.Value b := elm.Value
t.mu.Lock()
delete(t.tuples, b.tuple) delete(t.tuples, b.tuple)
t.tuples[tuple] = elm t.tuples[tuple] = elm
t.mu.Unlock()
b.tuple = tuple b.tuple = tuple
t.available.MoveToFront(elm) t.available.MoveToFront(elm)
@ -71,19 +66,6 @@ func (t *table) newConn(tuple tuple) uint16 {
return portBegin + b.offset return portBegin + b.offset
} }
func (t *table) delete(tup tuple) {
t.mu.Lock()
elm := t.tuples[tup]
if elm == nil {
t.mu.Unlock()
return
}
delete(t.tuples, tup)
t.mu.Unlock()
t.available.MoveToBack(elm)
}
func newTable() *table { func newTable() *table {
result := &table{ result := &table{
tuples: make(map[tuple]*list.Element[*binding], portLength), tuples: make(map[tuple]*list.Element[*binding], portLength),

View file

@ -16,8 +16,6 @@ type conn struct {
net.Conn net.Conn
tuple tuple tuple tuple
close func(tuple tuple)
} }
func (t *TCP) Accept() (net.Conn, error) { func (t *TCP) Accept() (net.Conn, error) {
@ -39,9 +37,6 @@ func (t *TCP) Accept() (net.Conn, error) {
return &conn{ return &conn{
Conn: c, Conn: c,
tuple: tup, tuple: tup,
close: func(tuple tuple) {
t.table.delete(tuple)
},
}, nil }, nil
} }
@ -57,11 +52,6 @@ func (t *TCP) SetDeadline(time time.Time) error {
return t.listener.SetDeadline(time) return t.listener.SetDeadline(time)
} }
func (c *conn) Close() error {
c.close(c.tuple)
return c.Conn.Close()
}
func (c *conn) LocalAddr() net.Addr { func (c *conn) LocalAddr() net.Addr {
return &net.TCPAddr{ return &net.TCPAddr{
IP: c.tuple.SourceAddr.Addr().AsSlice(), IP: c.tuple.SourceAddr.Addr().AsSlice(),

View file

@ -2,7 +2,6 @@ package tunnel
import ( import (
"errors" "errors"
"io"
"net" "net"
"time" "time"
@ -63,35 +62,5 @@ func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string, fAddr n
} }
func handleSocket(ctx C.ConnContext, outbound net.Conn) { func handleSocket(ctx C.ConnContext, outbound net.Conn) {
relay(ctx.Conn(), outbound) N.Relay(ctx.Conn(), outbound)
}
// relay copies between left and right bidirectionally.
func relay(leftConn, rightConn net.Conn) {
ch := make(chan error)
tcpKeepAlive(leftConn)
tcpKeepAlive(rightConn)
go func() {
buf := pool.Get(pool.RelayBufferSize)
// Wrapping to avoid using *net.TCPConn.(ReadFrom)
// See also https://github.com/Dreamacro/clash/pull/1209
_, err := io.CopyBuffer(N.WriteOnlyWriter{Writer: leftConn}, N.ReadOnlyReader{Reader: rightConn}, buf)
pool.Put(buf)
leftConn.SetReadDeadline(time.Now())
ch <- err
}()
buf := pool.Get(pool.RelayBufferSize)
io.CopyBuffer(N.WriteOnlyWriter{Writer: rightConn}, N.ReadOnlyReader{Reader: leftConn}, buf)
pool.Put(buf)
rightConn.SetReadDeadline(time.Now())
<-ch
}
func tcpKeepAlive(c net.Conn) {
if tcp, ok := c.(*net.TCPConn); ok {
tcp.SetKeepAlive(true)
}
} }