Compare commits
17 Commits
Author | SHA1 | Date | |
---|---|---|---|
502aa61c0e | |||
cc6d496143 | |||
10e0231bc1 | |||
fd63707399 | |||
c5757a9b11 | |||
370bc769d5 | |||
ce7cb138d4 | |||
d2174149c1 | |||
bcba14e05e | |||
19cbe52456 | |||
e12d46f619 | |||
990bba4a05 | |||
03c563a58e | |||
f943f9284d | |||
1f556d4ae5 | |||
082d3bbf04 | |||
4895bcefca |
18
Dockerfile
18
Dockerfile
@ -1,17 +1,23 @@
|
|||||||
FROM golang:latest as builder
|
FROM golang:latest as builder
|
||||||
|
|
||||||
RUN wget http://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.tar.gz -O /tmp/GeoLite2-Country.tar.gz && \
|
RUN wget http://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.tar.gz -O /tmp/GeoLite2-Country.tar.gz && \
|
||||||
tar zxvf /tmp/GeoLite2-Country.tar.gz -C /tmp && \
|
tar zxvf /tmp/GeoLite2-Country.tar.gz -C /tmp && \
|
||||||
cp /tmp/GeoLite2-Country_*/GeoLite2-Country.mmdb /Country.mmdb
|
mv /tmp/GeoLite2-Country_*/GeoLite2-Country.mmdb /Country.mmdb
|
||||||
|
|
||||||
WORKDIR /clash-src
|
WORKDIR /clash-src
|
||||||
|
|
||||||
COPY . /clash-src
|
COPY . /clash-src
|
||||||
|
|
||||||
RUN go mod download && \
|
RUN go mod download && \
|
||||||
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '-w -s' -o /clash && \
|
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '-w -s' -o /clash
|
||||||
chmod +x /clash
|
|
||||||
|
|
||||||
FROM alpine:latest
|
FROM alpine:latest
|
||||||
RUN apk --no-cache add ca-certificates && \
|
|
||||||
mkdir -p /root/.config/clash
|
RUN apk add --no-cache ca-certificates
|
||||||
|
|
||||||
COPY --from=builder /Country.mmdb /root/.config/clash/
|
COPY --from=builder /Country.mmdb /root/.config/clash/
|
||||||
COPY --from=builder /clash .
|
COPY --from=builder /clash /
|
||||||
|
|
||||||
EXPOSE 7890 7891
|
EXPOSE 7890 7891
|
||||||
|
|
||||||
ENTRYPOINT ["/clash"]
|
ENTRYPOINT ["/clash"]
|
||||||
|
19
README.md
19
README.md
@ -101,7 +101,7 @@ log-level: info
|
|||||||
external-controller: 127.0.0.1:9090
|
external-controller: 127.0.0.1:9090
|
||||||
|
|
||||||
# Secret for RESTful API (Optional)
|
# Secret for RESTful API (Optional)
|
||||||
secret: ""
|
# secret: ""
|
||||||
|
|
||||||
Proxy:
|
Proxy:
|
||||||
|
|
||||||
@ -114,11 +114,22 @@ Proxy:
|
|||||||
|
|
||||||
# vmess
|
# vmess
|
||||||
# cipher support auto/aes-128-gcm/chacha20-poly1305/none
|
# cipher support auto/aes-128-gcm/chacha20-poly1305/none
|
||||||
- { name: "vmess1", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto }
|
- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto }
|
||||||
- { name: "vmess2", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, tls: true }
|
# with tls
|
||||||
|
- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, tls: true }
|
||||||
|
# with tls and skip-cert-verify
|
||||||
|
- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, tls: true, skip-cert-verify: true }
|
||||||
|
# with ws
|
||||||
|
- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, network: ws, ws-path: /path }
|
||||||
|
# with ws + tls
|
||||||
|
- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, network: ws, ws-path: /path, tls: true }
|
||||||
|
|
||||||
# socks5
|
# socks5
|
||||||
- { name: "socks", type: socks5, server: server, port: 443 }
|
- { name: "socks", type: socks5, server: server, port: 443 }
|
||||||
|
# with tls
|
||||||
|
- { name: "socks", type: socks5, server: server, port: 443, tls: true }
|
||||||
|
# with tls and skip-cert-verify
|
||||||
|
- { name: "socks", type: socks5, server: server, port: 443, tls: true, skip-cert-verify: true }
|
||||||
|
|
||||||
Proxy Group:
|
Proxy Group:
|
||||||
# url-test select which proxy will be used by benchmarking speed to a URL.
|
# url-test select which proxy will be used by benchmarking speed to a URL.
|
||||||
@ -134,7 +145,9 @@ Proxy Group:
|
|||||||
Rule:
|
Rule:
|
||||||
- DOMAIN-SUFFIX,google.com,Proxy
|
- DOMAIN-SUFFIX,google.com,Proxy
|
||||||
- DOMAIN-KEYWORD,google,Proxy
|
- DOMAIN-KEYWORD,google,Proxy
|
||||||
|
- DOMAIN,google.com,Proxy
|
||||||
- DOMAIN-SUFFIX,ad.com,REJECT
|
- DOMAIN-SUFFIX,ad.com,REJECT
|
||||||
|
- IP-CIDR,127.0.0.0/8,DIRECT
|
||||||
- GEOIP,CN,DIRECT
|
- GEOIP,CN,DIRECT
|
||||||
# note: there is two ","
|
# note: there is two ","
|
||||||
- FINAL,,Proxy
|
- FINAL,,Proxy
|
||||||
|
@ -32,7 +32,7 @@ func (d *Direct) Type() C.AdapterType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Direct) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) {
|
func (d *Direct) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) {
|
||||||
c, err := net.Dial("tcp", net.JoinHostPort(metadata.String(), metadata.Port))
|
c, err := net.DialTimeout("tcp", net.JoinHostPort(metadata.String(), metadata.Port), tcpTimeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,7 @@ func (ss *ShadowSocks) Type() C.AdapterType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ss *ShadowSocks) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) {
|
func (ss *ShadowSocks) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) {
|
||||||
c, err := net.Dial("tcp", ss.server)
|
c, err := net.DialTimeout("tcp", ss.server, tcpTimeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error", ss.server)
|
return nil, fmt.Errorf("%s connect error", ss.server)
|
||||||
}
|
}
|
||||||
@ -72,7 +72,7 @@ func (ss *ShadowSocks) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||||
server := fmt.Sprintf("%s:%d", option.Server, option.Port)
|
server := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||||
cipher := option.Cipher
|
cipher := option.Cipher
|
||||||
password := option.Password
|
password := option.Password
|
||||||
ciph, err := core.PickCipher(cipher, nil, password)
|
ciph, err := core.PickCipher(cipher, nil, password)
|
||||||
|
@ -2,10 +2,12 @@ package adapters
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
|
||||||
@ -27,14 +29,19 @@ func (ss *Socks5Adapter) Conn() net.Conn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Socks5 struct {
|
type Socks5 struct {
|
||||||
addr string
|
addr string
|
||||||
name string
|
name string
|
||||||
|
tls bool
|
||||||
|
skipCertVerify bool
|
||||||
|
tlsConfig *tls.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
type Socks5Option struct {
|
type Socks5Option struct {
|
||||||
Name string `proxy:"name"`
|
Name string `proxy:"name"`
|
||||||
Server string `proxy:"server"`
|
Server string `proxy:"server"`
|
||||||
Port int `proxy:"port"`
|
Port int `proxy:"port"`
|
||||||
|
TLS bool `proxy:"tls,omitempty"`
|
||||||
|
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ss *Socks5) Name() string {
|
func (ss *Socks5) Name() string {
|
||||||
@ -46,7 +53,14 @@ func (ss *Socks5) Type() C.AdapterType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ss *Socks5) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) {
|
func (ss *Socks5) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) {
|
||||||
c, err := net.Dial("tcp", ss.addr)
|
c, err := net.DialTimeout("tcp", ss.addr, tcpTimeout)
|
||||||
|
|
||||||
|
if err == nil && ss.tls {
|
||||||
|
cc := tls.Client(c, ss.tlsConfig)
|
||||||
|
err = cc.Handshake()
|
||||||
|
c = cc
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error", ss.addr)
|
return nil, fmt.Errorf("%s connect error", ss.addr)
|
||||||
}
|
}
|
||||||
@ -89,8 +103,22 @@ func (ss *Socks5) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewSocks5(option Socks5Option) *Socks5 {
|
func NewSocks5(option Socks5Option) *Socks5 {
|
||||||
|
var tlsConfig *tls.Config
|
||||||
|
if option.TLS {
|
||||||
|
tlsConfig = &tls.Config{
|
||||||
|
InsecureSkipVerify: option.SkipCertVerify,
|
||||||
|
ClientSessionCache: getClientSessionCache(),
|
||||||
|
MinVersion: tls.VersionTLS11,
|
||||||
|
MaxVersion: tls.VersionTLS12,
|
||||||
|
ServerName: option.Server,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return &Socks5{
|
return &Socks5{
|
||||||
addr: fmt.Sprintf("%s:%d", option.Server, option.Port),
|
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
||||||
name: option.Name,
|
name: option.Name,
|
||||||
|
tls: option.TLS,
|
||||||
|
skipCertVerify: option.SkipCertVerify,
|
||||||
|
tlsConfig: tlsConfig,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package adapters
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
@ -15,6 +16,7 @@ type URLTest struct {
|
|||||||
fast C.Proxy
|
fast C.Proxy
|
||||||
interval time.Duration
|
interval time.Duration
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
|
once int32
|
||||||
}
|
}
|
||||||
|
|
||||||
type URLTestOption struct {
|
type URLTestOption struct {
|
||||||
@ -37,7 +39,11 @@ func (u *URLTest) Now() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *URLTest) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) {
|
func (u *URLTest) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) {
|
||||||
return u.fast.Generator(metadata)
|
a, err := u.fast.Generator(metadata)
|
||||||
|
if err != nil {
|
||||||
|
go u.speedTest()
|
||||||
|
}
|
||||||
|
return a, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *URLTest) Close() {
|
func (u *URLTest) Close() {
|
||||||
@ -59,6 +65,11 @@ Loop:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *URLTest) speedTest() {
|
func (u *URLTest) speedTest() {
|
||||||
|
if atomic.AddInt32(&u.once, 1) != 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer atomic.StoreInt32(&u.once, 0)
|
||||||
|
|
||||||
wg := sync.WaitGroup{}
|
wg := sync.WaitGroup{}
|
||||||
wg.Add(len(u.proxies))
|
wg.Add(len(u.proxies))
|
||||||
c := make(chan interface{})
|
c := make(chan interface{})
|
||||||
@ -108,6 +119,7 @@ func NewURLTest(option URLTestOption, proxies []C.Proxy) (*URLTest, error) {
|
|||||||
fast: proxies[0],
|
fast: proxies[0],
|
||||||
interval: interval,
|
interval: interval,
|
||||||
done: make(chan struct{}),
|
done: make(chan struct{}),
|
||||||
|
once: 0,
|
||||||
}
|
}
|
||||||
go urlTest.loop()
|
go urlTest.loop()
|
||||||
return urlTest, nil
|
return urlTest, nil
|
||||||
|
@ -1,15 +1,26 @@
|
|||||||
package adapters
|
package adapters
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
tcpTimeout = 5 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
globalClientSessionCache tls.ClientSessionCache
|
||||||
|
once sync.Once
|
||||||
|
)
|
||||||
|
|
||||||
// DelayTest get the delay for the specified URL
|
// DelayTest get the delay for the specified URL
|
||||||
func DelayTest(proxy C.Proxy, url string) (t int16, err error) {
|
func DelayTest(proxy C.Proxy, url string) (t int16, err error) {
|
||||||
addr, err := urlToMetadata(url)
|
addr, err := urlToMetadata(url)
|
||||||
@ -91,3 +102,10 @@ func tcpKeepAlive(c net.Conn) {
|
|||||||
tcp.SetKeepAlivePeriod(30 * time.Second)
|
tcp.SetKeepAlivePeriod(30 * time.Second)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getClientSessionCache() tls.ClientSessionCache {
|
||||||
|
once.Do(func() {
|
||||||
|
globalClientSessionCache = tls.NewLRUClientSessionCache(128)
|
||||||
|
})
|
||||||
|
return globalClientSessionCache
|
||||||
|
}
|
||||||
|
@ -31,13 +31,16 @@ type Vmess struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type VmessOption struct {
|
type VmessOption struct {
|
||||||
Name string `proxy:"name"`
|
Name string `proxy:"name"`
|
||||||
Server string `proxy:"server"`
|
Server string `proxy:"server"`
|
||||||
Port int `proxy:"port"`
|
Port int `proxy:"port"`
|
||||||
UUID string `proxy:"uuid"`
|
UUID string `proxy:"uuid"`
|
||||||
AlterID int `proxy:"alterId"`
|
AlterID int `proxy:"alterId"`
|
||||||
Cipher string `proxy:"cipher"`
|
Cipher string `proxy:"cipher"`
|
||||||
TLS bool `proxy:"tls,omitempty"`
|
TLS bool `proxy:"tls,omitempty"`
|
||||||
|
Network string `proxy:"network,omitempty"`
|
||||||
|
WSPath string `proxy:"ws-path,omitempty"`
|
||||||
|
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ss *Vmess) Name() string {
|
func (ss *Vmess) Name() string {
|
||||||
@ -49,22 +52,27 @@ func (ss *Vmess) Type() C.AdapterType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ss *Vmess) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) {
|
func (ss *Vmess) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) {
|
||||||
c, err := net.Dial("tcp", ss.server)
|
c, err := net.DialTimeout("tcp", ss.server, tcpTimeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error", ss.server)
|
return nil, fmt.Errorf("%s connect error", ss.server)
|
||||||
}
|
}
|
||||||
tcpKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
c = ss.client.New(c, parseVmessAddr(metadata))
|
c, err = ss.client.New(c, parseVmessAddr(metadata))
|
||||||
return &VmessAdapter{conn: c}, err
|
return &VmessAdapter{conn: c}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
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: option.UUID,
|
||||||
AlterID: uint16(option.AlterID),
|
AlterID: uint16(option.AlterID),
|
||||||
Security: security,
|
Security: security,
|
||||||
TLS: option.TLS,
|
TLS: option.TLS,
|
||||||
|
Host: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
||||||
|
NetWork: option.Network,
|
||||||
|
WebSocketPath: option.WSPath,
|
||||||
|
SkipCertVerify: option.SkipCertVerify,
|
||||||
|
SessionCacahe: getClientSessionCache(),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -72,7 +80,7 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
|||||||
|
|
||||||
return &Vmess{
|
return &Vmess{
|
||||||
name: option.Name,
|
name: option.Name,
|
||||||
server: fmt.Sprintf("%s:%d", option.Server, option.Port),
|
server: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
||||||
client: client,
|
client: client,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -65,11 +65,10 @@ func (vc *Conn) Read(b []byte) (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (vc *Conn) sendRequest() error {
|
func (vc *Conn) sendRequest() error {
|
||||||
timestamp := make([]byte, 8)
|
timestamp := time.Now()
|
||||||
binary.BigEndian.PutUint64(timestamp, uint64(time.Now().UTC().Unix()))
|
|
||||||
|
|
||||||
h := hmac.New(md5.New, vc.id.UUID.Bytes())
|
h := hmac.New(md5.New, vc.id.UUID.Bytes())
|
||||||
h.Write(timestamp)
|
binary.Write(h, binary.BigEndian, uint64(timestamp.Unix()))
|
||||||
_, err := vc.Conn.Write(h.Sum(nil))
|
_, err := vc.Conn.Write(h.Sum(nil))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -111,7 +110,7 @@ func (vc *Conn) sendRequest() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
stream := cipher.NewCFBEncrypter(block, hashTimestamp(time.Now().UTC()))
|
stream := cipher.NewCFBEncrypter(block, hashTimestamp(timestamp))
|
||||||
stream.XORKeyStream(buf.Bytes(), buf.Bytes())
|
stream.XORKeyStream(buf.Bytes(), buf.Bytes())
|
||||||
_, err = vc.Conn.Write(buf.Bytes())
|
_, err = vc.Conn.Write(buf.Bytes())
|
||||||
return err
|
return err
|
||||||
@ -145,7 +144,7 @@ func (vc *Conn) recvResponse() error {
|
|||||||
func hashTimestamp(t time.Time) []byte {
|
func hashTimestamp(t time.Time) []byte {
|
||||||
md5hash := md5.New()
|
md5hash := md5.New()
|
||||||
ts := make([]byte, 8)
|
ts := make([]byte, 8)
|
||||||
binary.BigEndian.PutUint64(ts, uint64(t.UTC().Unix()))
|
binary.BigEndian.PutUint64(ts, uint64(t.Unix()))
|
||||||
md5hash.Write(ts)
|
md5hash.Write(ts)
|
||||||
md5hash.Write(ts)
|
md5hash.Write(ts)
|
||||||
md5hash.Write(ts)
|
md5hash.Write(ts)
|
||||||
|
@ -50,5 +50,6 @@ func newAlterIDs(primary *ID, alterIDCount uint16) []*ID {
|
|||||||
alterIDs[idx] = &ID{UUID: newid, CmdKey: primary.CmdKey[:]}
|
alterIDs[idx] = &ID{UUID: newid, CmdKey: primary.CmdKey[:]}
|
||||||
prevID = newid
|
prevID = newid
|
||||||
}
|
}
|
||||||
|
alterIDs = append(alterIDs, primary)
|
||||||
return alterIDs
|
return alterIDs
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/gofrs/uuid"
|
"github.com/gofrs/uuid"
|
||||||
)
|
)
|
||||||
@ -36,9 +37,10 @@ var CipherMapping = map[string]byte{
|
|||||||
"chacha20-poly1305": SecurityCHACHA20POLY1305,
|
"chacha20-poly1305": SecurityCHACHA20POLY1305,
|
||||||
}
|
}
|
||||||
|
|
||||||
var tlsConfig = &tls.Config{
|
var (
|
||||||
InsecureSkipVerify: true,
|
clientSessionCache tls.ClientSessionCache
|
||||||
}
|
once sync.Once
|
||||||
|
)
|
||||||
|
|
||||||
// Command types
|
// Command types
|
||||||
const (
|
const (
|
||||||
@ -62,27 +64,41 @@ type DstAddr struct {
|
|||||||
|
|
||||||
// Client is vmess connection generator
|
// Client is vmess connection generator
|
||||||
type Client struct {
|
type Client struct {
|
||||||
user []*ID
|
user []*ID
|
||||||
uuid *uuid.UUID
|
uuid *uuid.UUID
|
||||||
security Security
|
security Security
|
||||||
tls bool
|
tls bool
|
||||||
|
host string
|
||||||
|
wsConfig *websocketConfig
|
||||||
|
tlsConfig *tls.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config of vmess
|
// Config of vmess
|
||||||
type Config struct {
|
type Config struct {
|
||||||
UUID string
|
UUID string
|
||||||
AlterID uint16
|
AlterID uint16
|
||||||
Security string
|
Security string
|
||||||
TLS bool
|
TLS bool
|
||||||
|
Host string
|
||||||
|
NetWork string
|
||||||
|
WebSocketPath string
|
||||||
|
SkipCertVerify bool
|
||||||
|
SessionCacahe tls.ClientSessionCache
|
||||||
}
|
}
|
||||||
|
|
||||||
// New return a Conn with net.Conn and DstAddr
|
// New return a Conn with net.Conn and DstAddr
|
||||||
func (c *Client) New(conn net.Conn, dst *DstAddr) net.Conn {
|
func (c *Client) New(conn net.Conn, dst *DstAddr) (net.Conn, error) {
|
||||||
|
var err error
|
||||||
r := rand.Intn(len(c.user))
|
r := rand.Intn(len(c.user))
|
||||||
if c.tls {
|
if c.wsConfig != nil {
|
||||||
conn = tls.Client(conn, tlsConfig)
|
conn, err = newWebsocketConn(conn, c.wsConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else if c.tls {
|
||||||
|
conn = tls.Client(conn, c.tlsConfig)
|
||||||
}
|
}
|
||||||
return newConn(conn, c.user[r], dst, c.security)
|
return newConn(conn, c.user[r], dst, c.security), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClient return Client instance
|
// NewClient return Client instance
|
||||||
@ -108,10 +124,46 @@ func NewClient(config Config) (*Client, error) {
|
|||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("Unknown security type: %s", config.Security)
|
return nil, fmt.Errorf("Unknown security type: %s", config.Security)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.NetWork != "" && config.NetWork != "ws" {
|
||||||
|
return nil, fmt.Errorf("Unknown network type: %s", config.NetWork)
|
||||||
|
}
|
||||||
|
|
||||||
|
var tlsConfig *tls.Config
|
||||||
|
if config.TLS {
|
||||||
|
tlsConfig = &tls.Config{
|
||||||
|
InsecureSkipVerify: config.SkipCertVerify,
|
||||||
|
ClientSessionCache: config.SessionCacahe,
|
||||||
|
}
|
||||||
|
if tlsConfig.ClientSessionCache == nil {
|
||||||
|
tlsConfig.ClientSessionCache = getClientSessionCache()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var wsConfig *websocketConfig
|
||||||
|
if config.NetWork == "ws" {
|
||||||
|
wsConfig = &websocketConfig{
|
||||||
|
host: config.Host,
|
||||||
|
path: config.WebSocketPath,
|
||||||
|
tls: config.TLS,
|
||||||
|
tlsConfig: tlsConfig,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return &Client{
|
return &Client{
|
||||||
user: newAlterIDs(newID(&uid), config.AlterID),
|
user: newAlterIDs(newID(&uid), config.AlterID),
|
||||||
uuid: &uid,
|
uuid: &uid,
|
||||||
security: security,
|
security: security,
|
||||||
tls: config.TLS,
|
tls: config.TLS,
|
||||||
|
host: config.Host,
|
||||||
|
wsConfig: wsConfig,
|
||||||
|
tlsConfig: tlsConfig,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getClientSessionCache() tls.ClientSessionCache {
|
||||||
|
once.Do(func() {
|
||||||
|
clientSessionCache = tls.NewLRUClientSessionCache(128)
|
||||||
|
})
|
||||||
|
return clientSessionCache
|
||||||
|
}
|
||||||
|
143
component/vmess/websocket.go
Normal file
143
component/vmess/websocket.go
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
package vmess
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
type websocketConn struct {
|
||||||
|
conn *websocket.Conn
|
||||||
|
reader io.Reader
|
||||||
|
remoteAddr net.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
type websocketConfig struct {
|
||||||
|
host string
|
||||||
|
path string
|
||||||
|
tls bool
|
||||||
|
tlsConfig *tls.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read implements net.Conn.Read()
|
||||||
|
func (wsc *websocketConn) Read(b []byte) (int, error) {
|
||||||
|
for {
|
||||||
|
reader, err := wsc.getReader()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nBytes, err := reader.Read(b)
|
||||||
|
if err == io.EOF {
|
||||||
|
wsc.reader = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return nBytes, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write implements io.Writer.
|
||||||
|
func (wsc *websocketConn) Write(b []byte) (int, error) {
|
||||||
|
if err := wsc.conn.WriteMessage(websocket.BinaryMessage, b); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return len(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wsc *websocketConn) Close() error {
|
||||||
|
var errors []string
|
||||||
|
if err := wsc.conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""), time.Now().Add(time.Second*5)); err != nil {
|
||||||
|
errors = append(errors, err.Error())
|
||||||
|
}
|
||||||
|
if err := wsc.conn.Close(); err != nil {
|
||||||
|
errors = append(errors, err.Error())
|
||||||
|
}
|
||||||
|
if len(errors) > 0 {
|
||||||
|
return fmt.Errorf("Failed to close connection: %s", strings.Join(errors, ","))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wsc *websocketConn) getReader() (io.Reader, error) {
|
||||||
|
if wsc.reader != nil {
|
||||||
|
return wsc.reader, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, reader, err := wsc.conn.NextReader()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
wsc.reader = reader
|
||||||
|
return reader, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wsc *websocketConn) LocalAddr() net.Addr {
|
||||||
|
return wsc.conn.LocalAddr()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wsc *websocketConn) RemoteAddr() net.Addr {
|
||||||
|
return wsc.remoteAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wsc *websocketConn) SetDeadline(t time.Time) error {
|
||||||
|
if err := wsc.SetReadDeadline(t); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return wsc.SetWriteDeadline(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wsc *websocketConn) SetReadDeadline(t time.Time) error {
|
||||||
|
return wsc.conn.SetReadDeadline(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wsc *websocketConn) SetWriteDeadline(t time.Time) error {
|
||||||
|
return wsc.conn.SetWriteDeadline(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newWebsocketConn(conn net.Conn, c *websocketConfig) (net.Conn, error) {
|
||||||
|
dialer := &websocket.Dialer{
|
||||||
|
NetDial: func(network, addr string) (net.Conn, error) {
|
||||||
|
return conn, nil
|
||||||
|
},
|
||||||
|
ReadBufferSize: 4 * 1024,
|
||||||
|
WriteBufferSize: 4 * 1024,
|
||||||
|
HandshakeTimeout: time.Second * 8,
|
||||||
|
}
|
||||||
|
|
||||||
|
scheme := "ws"
|
||||||
|
if c.tls {
|
||||||
|
scheme = "wss"
|
||||||
|
dialer.TLSClientConfig = c.tlsConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
host, port, err := net.SplitHostPort(c.host)
|
||||||
|
if (scheme == "ws" && port != "80") || (scheme == "wss" && port != "443") {
|
||||||
|
host = c.host
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := url.URL{
|
||||||
|
Scheme: scheme,
|
||||||
|
Host: host,
|
||||||
|
Path: c.path,
|
||||||
|
}
|
||||||
|
|
||||||
|
wsConn, resp, err := dialer.Dial(uri.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
var reason string
|
||||||
|
if resp != nil {
|
||||||
|
reason = resp.Status
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("Dial %s error: %s", host, reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &websocketConn{
|
||||||
|
conn: wsConn,
|
||||||
|
remoteAddr: conn.RemoteAddr(),
|
||||||
|
}, nil
|
||||||
|
}
|
@ -250,14 +250,10 @@ func (c *Config) parseProxies(cfg *RawConfig) error {
|
|||||||
// parse proxy
|
// parse proxy
|
||||||
for idx, mapping := range proxiesConfig {
|
for idx, mapping := range proxiesConfig {
|
||||||
proxyType, existType := mapping["type"].(string)
|
proxyType, existType := mapping["type"].(string)
|
||||||
proxyName, existName := mapping["name"].(string)
|
if !existType {
|
||||||
if !existType && existName {
|
return fmt.Errorf("Proxy %d missing type", idx)
|
||||||
return fmt.Errorf("Proxy %d missing type or name", idx)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, exist := proxies[proxyName]; exist {
|
|
||||||
return fmt.Errorf("Proxy %s is the duplicate name", proxyName)
|
|
||||||
}
|
|
||||||
var proxy C.Proxy
|
var proxy C.Proxy
|
||||||
var err error
|
var err error
|
||||||
switch proxyType {
|
switch proxyType {
|
||||||
@ -285,10 +281,15 @@ func (c *Config) parseProxies(cfg *RawConfig) error {
|
|||||||
default:
|
default:
|
||||||
return fmt.Errorf("Unsupport proxy type: %s", proxyType)
|
return fmt.Errorf("Unsupport proxy type: %s", proxyType)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Proxy %s: %s", proxyName, err.Error())
|
return fmt.Errorf("Proxy [%d]: %s", idx, err.Error())
|
||||||
}
|
}
|
||||||
proxies[proxyName] = proxy
|
|
||||||
|
if _, exist := proxies[proxy.Name()]; exist {
|
||||||
|
return fmt.Errorf("Proxy %s is the duplicate name", proxy.Name())
|
||||||
|
}
|
||||||
|
proxies[proxy.Name()] = proxy
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse proxy group
|
// parse proxy group
|
||||||
|
1
go.mod
1
go.mod
@ -7,6 +7,7 @@ require (
|
|||||||
github.com/go-chi/cors v1.0.0
|
github.com/go-chi/cors v1.0.0
|
||||||
github.com/go-chi/render v1.0.1
|
github.com/go-chi/render v1.0.1
|
||||||
github.com/gofrs/uuid v3.1.0+incompatible
|
github.com/gofrs/uuid v3.1.0+incompatible
|
||||||
|
github.com/gorilla/websocket v1.4.0
|
||||||
github.com/oschwald/geoip2-golang v1.2.1
|
github.com/oschwald/geoip2-golang v1.2.1
|
||||||
github.com/oschwald/maxminddb-golang v1.3.0 // indirect
|
github.com/oschwald/maxminddb-golang v1.3.0 // indirect
|
||||||
github.com/sirupsen/logrus v1.1.0
|
github.com/sirupsen/logrus v1.1.0
|
||||||
|
2
go.sum
2
go.sum
@ -14,6 +14,8 @@ github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8=
|
|||||||
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
|
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
|
||||||
github.com/gofrs/uuid v3.1.0+incompatible h1:q2rtkjaKT4YEr6E1kamy0Ha4RtepWlQBedyHx0uzKwA=
|
github.com/gofrs/uuid v3.1.0+incompatible h1:q2rtkjaKT4YEr6E1kamy0Ha4RtepWlQBedyHx0uzKwA=
|
||||||
github.com/gofrs/uuid v3.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
github.com/gofrs/uuid v3.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
|
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
||||||
|
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe h1:CHRGQ8V7OlCYtwaKPJi3iA7J+YdNKdo8j7nG5IgDhjs=
|
github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe h1:CHRGQ8V7OlCYtwaKPJi3iA7J+YdNKdo8j7nG5IgDhjs=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/oschwald/geoip2-golang v1.2.1 h1:3iz+jmeJc6fuCyWeKgtXSXu7+zvkxJbHFXkMT5FVebU=
|
github.com/oschwald/geoip2-golang v1.2.1 h1:3iz+jmeJc6fuCyWeKgtXSXu7+zvkxJbHFXkMT5FVebU=
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
package hub
|
package hub
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -16,12 +18,24 @@ import (
|
|||||||
func proxyRouter() http.Handler {
|
func proxyRouter() http.Handler {
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
r.Get("/", getProxies)
|
r.Get("/", getProxies)
|
||||||
r.Get("/{name}", getProxy)
|
r.With(parseProxyName).Get("/{name}", getProxy)
|
||||||
r.Get("/{name}/delay", getProxyDelay)
|
r.With(parseProxyName).Get("/{name}/delay", getProxyDelay)
|
||||||
r.Put("/{name}", updateProxy)
|
r.With(parseProxyName).Put("/{name}", updateProxy)
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When name is composed of a partial escape string, Golang does not unescape it
|
||||||
|
func parseProxyName(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
name := chi.URLParam(r, "name")
|
||||||
|
if newName, err := url.PathUnescape(name); err == nil {
|
||||||
|
name = newName
|
||||||
|
}
|
||||||
|
ctx := context.WithValue(r.Context(), contextKey("proxy name"), name)
|
||||||
|
next.ServeHTTP(w, r.WithContext(ctx))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
type SampleProxy struct {
|
type SampleProxy struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
}
|
}
|
||||||
@ -83,7 +97,7 @@ func getProxies(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getProxy(w http.ResponseWriter, r *http.Request) {
|
func getProxy(w http.ResponseWriter, r *http.Request) {
|
||||||
name := chi.URLParam(r, "name")
|
name := r.Context().Value(contextKey("proxy name")).(string)
|
||||||
proxies := cfg.Proxies()
|
proxies := cfg.Proxies()
|
||||||
proxy, exist := proxies[name]
|
proxy, exist := proxies[name]
|
||||||
if !exist {
|
if !exist {
|
||||||
@ -110,7 +124,7 @@ func updateProxy(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
name := chi.URLParam(r, "name")
|
name := r.Context().Value(contextKey("proxy name")).(string)
|
||||||
proxies := cfg.Proxies()
|
proxies := cfg.Proxies()
|
||||||
proxy, exist := proxies[name]
|
proxy, exist := proxies[name]
|
||||||
if !exist {
|
if !exist {
|
||||||
@ -162,7 +176,7 @@ func getProxyDelay(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
name := chi.URLParam(r, "name")
|
name := r.Context().Value(contextKey("proxy name")).(string)
|
||||||
proxies := cfg.Proxies()
|
proxies := cfg.Proxies()
|
||||||
proxy, exist := proxies[name]
|
proxy, exist := proxies[name]
|
||||||
if !exist {
|
if !exist {
|
||||||
|
@ -15,8 +15,8 @@ func ruleRouter() http.Handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Rule struct {
|
type Rule struct {
|
||||||
Name string `json:"name"`
|
Type string `json:"type"`
|
||||||
Payload string `json:"type"`
|
Payload string `json:"payload"`
|
||||||
Proxy string `json:"proxy"`
|
Proxy string `json:"proxy"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ func getRules(w http.ResponseWriter, r *http.Request) {
|
|||||||
var rules []Rule
|
var rules []Rule
|
||||||
for _, rule := range rawRules {
|
for _, rule := range rawRules {
|
||||||
rules = append(rules, Rule{
|
rules = append(rules, Rule{
|
||||||
Name: rule.RuleType().String(),
|
Type: rule.RuleType().String(),
|
||||||
Payload: rule.Payload(),
|
Payload: rule.Payload(),
|
||||||
Proxy: rule.Adapter(),
|
Proxy: rule.Adapter(),
|
||||||
})
|
})
|
||||||
|
@ -100,6 +100,12 @@ func authentication(next http.Handler) http.Handler {
|
|||||||
return http.HandlerFunc(fn)
|
return http.HandlerFunc(fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type contextKey string
|
||||||
|
|
||||||
|
func (c contextKey) String() string {
|
||||||
|
return "clash context key " + string(c)
|
||||||
|
}
|
||||||
|
|
||||||
func traffic(w http.ResponseWriter, r *http.Request) {
|
func traffic(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
|
|
||||||
|
@ -5,12 +5,22 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapters/inbound"
|
"github.com/Dreamacro/clash/adapters/inbound"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// io.Copy default buffer size is 32 KiB
|
||||||
|
// but the maximum packet size of vmess/shadowsocks is about 16 KiB
|
||||||
|
// so define a buffer of 20 KiB to reduce the memory of each TCP relay
|
||||||
|
bufferSize = 20 * 1024
|
||||||
|
)
|
||||||
|
|
||||||
|
var bufPool = sync.Pool{New: func() interface{} { return make([]byte, bufferSize) }}
|
||||||
|
|
||||||
func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, proxy C.ProxyAdapter) {
|
func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, proxy C.ProxyAdapter) {
|
||||||
conn := newTrafficTrack(proxy.Conn(), t.traffic)
|
conn := newTrafficTrack(proxy.Conn(), t.traffic)
|
||||||
req := request.R
|
req := request.R
|
||||||
@ -66,12 +76,16 @@ func relay(leftConn, rightConn net.Conn) {
|
|||||||
ch := make(chan error)
|
ch := make(chan error)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
_, err := io.Copy(leftConn, rightConn)
|
buf := bufPool.Get().([]byte)
|
||||||
|
_, err := io.CopyBuffer(leftConn, rightConn, buf)
|
||||||
|
bufPool.Put(buf[:cap(buf)])
|
||||||
leftConn.SetReadDeadline(time.Now())
|
leftConn.SetReadDeadline(time.Now())
|
||||||
ch <- err
|
ch <- err
|
||||||
}()
|
}()
|
||||||
|
|
||||||
io.Copy(rightConn, leftConn)
|
buf := bufPool.Get().([]byte)
|
||||||
|
io.CopyBuffer(rightConn, leftConn, buf)
|
||||||
|
bufPool.Put(buf[:cap(buf)])
|
||||||
rightConn.SetReadDeadline(time.Now())
|
rightConn.SetReadDeadline(time.Now())
|
||||||
<-ch
|
<-ch
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user