Merge remote-tracking branch 'meta/Alpha' into Alpha
This commit is contained in:
commit
a38b2bcb6d
17 changed files with 298 additions and 147 deletions
28
.github/workflows/alpha.yml
vendored
28
.github/workflows/alpha.yml
vendored
|
@ -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}}
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
30
common/net/relay.go
Normal 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
|
||||||
|
}
|
|
@ -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:]
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
@ -31,7 +32,8 @@ type SnifferDispatcher struct {
|
||||||
foreDomain *trie.DomainTrie[bool]
|
foreDomain *trie.DomainTrie[bool]
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
95
listener/http/upgrade.go
Normal 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
|
||||||
|
}
|
|
@ -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")
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue