Compare commits

...

17 Commits

Author SHA1 Message Date
502aa61c0e Fix: vmess small probability invalid auth 2018-11-06 17:34:19 +08:00
cc6d496143 Chore: optimize code structure in vmess websocket (#28)
* Chore: move conn process of ws to websocket.go

* Chore: some routine adjustment
2018-11-04 21:36:20 +08:00
10e0231bc1 Fix: dial IPv6 host (#29) 2018-11-04 21:12:16 +08:00
fd63707399 Optimization: use client session cache for TLS connection (#26) 2018-11-01 11:54:45 +08:00
c5757a9b11 Chore: delete redundant print 2018-10-30 10:50:57 +08:00
370bc769d5 Update: README.md 2018-10-29 20:25:13 +08:00
ce7cb138d4 Chore: unified naming "skip-cert-verify" 2018-10-29 20:16:43 +08:00
d2174149c1 Feature: vmess add websocket support 2018-10-28 23:46:32 +08:00
bcba14e05e Improve: add tls, sni options to socks5 outbound adapter 2018-10-28 19:46:49 +08:00
19cbe52456 Fix: weak type proxy name 2018-10-27 12:57:56 +08:00
e12d46f619 Fix: unescape proxy name in /proixes 2018-10-27 12:36:33 +08:00
990bba4a05 Fix: GET /rules format 2018-10-25 00:09:55 +08:00
03c563a58e Improve: url-test will automatically speed test when the connection fails 2018-10-24 17:06:08 +08:00
f943f9284d Chore: clean up Dockerfile 2018-10-23 13:26:05 +08:00
1f556d4ae5 Fix: vmess alterId can be 0 2018-10-23 11:28:41 +08:00
082d3bbf04 Chore: adjust dial tcp timeout 2018-10-22 21:14:22 +08:00
4895bcefca Optimization: reduce the memory of each TCP relay 2018-10-21 20:28:40 +08:00
19 changed files with 396 additions and 78 deletions

View File

@ -1,17 +1,23 @@
FROM golang:latest as builder
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 && \
cp /tmp/GeoLite2-Country_*/GeoLite2-Country.mmdb /Country.mmdb
mv /tmp/GeoLite2-Country_*/GeoLite2-Country.mmdb /Country.mmdb
WORKDIR /clash-src
COPY . /clash-src
RUN go mod download && \
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '-w -s' -o /clash && \
chmod +x /clash
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '-w -s' -o /clash
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 /clash .
COPY --from=builder /clash /
EXPOSE 7890 7891
ENTRYPOINT ["/clash"]

View File

@ -101,7 +101,7 @@ log-level: info
external-controller: 127.0.0.1:9090
# Secret for RESTful API (Optional)
secret: ""
# secret: ""
Proxy:
@ -114,11 +114,22 @@ Proxy:
# vmess
# cipher support auto/aes-128-gcm/chacha20-poly1305/none
- { name: "vmess1", 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 }
- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto }
# 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
- { 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:
# url-test select which proxy will be used by benchmarking speed to a URL.
@ -134,7 +145,9 @@ Proxy Group:
Rule:
- DOMAIN-SUFFIX,google.com,Proxy
- DOMAIN-KEYWORD,google,Proxy
- DOMAIN,google.com,Proxy
- DOMAIN-SUFFIX,ad.com,REJECT
- IP-CIDR,127.0.0.0/8,DIRECT
- GEOIP,CN,DIRECT
# note: there is two ","
- FINAL,,Proxy

View File

@ -32,7 +32,7 @@ func (d *Direct) Type() C.AdapterType {
}
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 {
return
}

View File

@ -54,7 +54,7 @@ func (ss *ShadowSocks) Type() C.AdapterType {
}
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 {
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) {
server := fmt.Sprintf("%s:%d", option.Server, option.Port)
server := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
cipher := option.Cipher
password := option.Password
ciph, err := core.PickCipher(cipher, nil, password)

View File

@ -2,10 +2,12 @@ package adapters
import (
"bytes"
"crypto/tls"
"errors"
"fmt"
"io"
"net"
"strconv"
C "github.com/Dreamacro/clash/constant"
@ -27,14 +29,19 @@ func (ss *Socks5Adapter) Conn() net.Conn {
}
type Socks5 struct {
addr string
name string
addr string
name string
tls bool
skipCertVerify bool
tlsConfig *tls.Config
}
type Socks5Option struct {
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
TLS bool `proxy:"tls,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
}
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) {
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 {
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 {
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{
addr: fmt.Sprintf("%s:%d", option.Server, option.Port),
name: option.Name,
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
name: option.Name,
tls: option.TLS,
skipCertVerify: option.SkipCertVerify,
tlsConfig: tlsConfig,
}
}

View File

@ -3,6 +3,7 @@ package adapters
import (
"errors"
"sync"
"sync/atomic"
"time"
C "github.com/Dreamacro/clash/constant"
@ -15,6 +16,7 @@ type URLTest struct {
fast C.Proxy
interval time.Duration
done chan struct{}
once int32
}
type URLTestOption struct {
@ -37,7 +39,11 @@ func (u *URLTest) Now() string {
}
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() {
@ -59,6 +65,11 @@ Loop:
}
func (u *URLTest) speedTest() {
if atomic.AddInt32(&u.once, 1) != 1 {
return
}
defer atomic.StoreInt32(&u.once, 0)
wg := sync.WaitGroup{}
wg.Add(len(u.proxies))
c := make(chan interface{})
@ -108,6 +119,7 @@ func NewURLTest(option URLTestOption, proxies []C.Proxy) (*URLTest, error) {
fast: proxies[0],
interval: interval,
done: make(chan struct{}),
once: 0,
}
go urlTest.loop()
return urlTest, nil

View File

@ -1,15 +1,26 @@
package adapters
import (
"crypto/tls"
"fmt"
"net"
"net/http"
"net/url"
"sync"
"time"
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
func DelayTest(proxy C.Proxy, url string) (t int16, err error) {
addr, err := urlToMetadata(url)
@ -91,3 +102,10 @@ func tcpKeepAlive(c net.Conn) {
tcp.SetKeepAlivePeriod(30 * time.Second)
}
}
func getClientSessionCache() tls.ClientSessionCache {
once.Do(func() {
globalClientSessionCache = tls.NewLRUClientSessionCache(128)
})
return globalClientSessionCache
}

View File

@ -31,13 +31,16 @@ type Vmess struct {
}
type VmessOption struct {
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
UUID string `proxy:"uuid"`
AlterID int `proxy:"alterId"`
Cipher string `proxy:"cipher"`
TLS bool `proxy:"tls,omitempty"`
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
UUID string `proxy:"uuid"`
AlterID int `proxy:"alterId"`
Cipher string `proxy:"cipher"`
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 {
@ -49,22 +52,27 @@ func (ss *Vmess) Type() C.AdapterType {
}
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 {
return nil, fmt.Errorf("%s connect error", ss.server)
}
tcpKeepAlive(c)
c = ss.client.New(c, parseVmessAddr(metadata))
c, err = ss.client.New(c, parseVmessAddr(metadata))
return &VmessAdapter{conn: c}, err
}
func NewVmess(option VmessOption) (*Vmess, error) {
security := strings.ToLower(option.Cipher)
client, err := vmess.NewClient(vmess.Config{
UUID: option.UUID,
AlterID: uint16(option.AlterID),
Security: security,
TLS: option.TLS,
UUID: option.UUID,
AlterID: uint16(option.AlterID),
Security: security,
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 {
return nil, err
@ -72,7 +80,7 @@ func NewVmess(option VmessOption) (*Vmess, error) {
return &Vmess{
name: option.Name,
server: fmt.Sprintf("%s:%d", option.Server, option.Port),
server: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
client: client,
}, nil
}

View File

@ -65,11 +65,10 @@ func (vc *Conn) Read(b []byte) (int, error) {
}
func (vc *Conn) sendRequest() error {
timestamp := make([]byte, 8)
binary.BigEndian.PutUint64(timestamp, uint64(time.Now().UTC().Unix()))
timestamp := time.Now()
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))
if err != nil {
return err
@ -111,7 +110,7 @@ func (vc *Conn) sendRequest() error {
return err
}
stream := cipher.NewCFBEncrypter(block, hashTimestamp(time.Now().UTC()))
stream := cipher.NewCFBEncrypter(block, hashTimestamp(timestamp))
stream.XORKeyStream(buf.Bytes(), buf.Bytes())
_, err = vc.Conn.Write(buf.Bytes())
return err
@ -145,7 +144,7 @@ func (vc *Conn) recvResponse() error {
func hashTimestamp(t time.Time) []byte {
md5hash := md5.New()
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)

View File

@ -50,5 +50,6 @@ func newAlterIDs(primary *ID, alterIDCount uint16) []*ID {
alterIDs[idx] = &ID{UUID: newid, CmdKey: primary.CmdKey[:]}
prevID = newid
}
alterIDs = append(alterIDs, primary)
return alterIDs
}

View File

@ -6,6 +6,7 @@ import (
"math/rand"
"net"
"runtime"
"sync"
"github.com/gofrs/uuid"
)
@ -36,9 +37,10 @@ var CipherMapping = map[string]byte{
"chacha20-poly1305": SecurityCHACHA20POLY1305,
}
var tlsConfig = &tls.Config{
InsecureSkipVerify: true,
}
var (
clientSessionCache tls.ClientSessionCache
once sync.Once
)
// Command types
const (
@ -62,27 +64,41 @@ type DstAddr struct {
// Client is vmess connection generator
type Client struct {
user []*ID
uuid *uuid.UUID
security Security
tls bool
user []*ID
uuid *uuid.UUID
security Security
tls bool
host string
wsConfig *websocketConfig
tlsConfig *tls.Config
}
// Config of vmess
type Config struct {
UUID string
AlterID uint16
Security string
TLS bool
UUID string
AlterID uint16
Security string
TLS bool
Host string
NetWork string
WebSocketPath string
SkipCertVerify bool
SessionCacahe tls.ClientSessionCache
}
// 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))
if c.tls {
conn = tls.Client(conn, tlsConfig)
if c.wsConfig != nil {
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
@ -108,10 +124,46 @@ func NewClient(config Config) (*Client, error) {
default:
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{
user: newAlterIDs(newID(&uid), config.AlterID),
uuid: &uid,
security: security,
tls: config.TLS,
user: newAlterIDs(newID(&uid), config.AlterID),
uuid: &uid,
security: security,
tls: config.TLS,
host: config.Host,
wsConfig: wsConfig,
tlsConfig: tlsConfig,
}, nil
}
func getClientSessionCache() tls.ClientSessionCache {
once.Do(func() {
clientSessionCache = tls.NewLRUClientSessionCache(128)
})
return clientSessionCache
}

View 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
}

View File

@ -250,14 +250,10 @@ func (c *Config) parseProxies(cfg *RawConfig) error {
// parse proxy
for idx, mapping := range proxiesConfig {
proxyType, existType := mapping["type"].(string)
proxyName, existName := mapping["name"].(string)
if !existType && existName {
return fmt.Errorf("Proxy %d missing type or name", idx)
if !existType {
return fmt.Errorf("Proxy %d missing type", idx)
}
if _, exist := proxies[proxyName]; exist {
return fmt.Errorf("Proxy %s is the duplicate name", proxyName)
}
var proxy C.Proxy
var err error
switch proxyType {
@ -285,10 +281,15 @@ func (c *Config) parseProxies(cfg *RawConfig) error {
default:
return fmt.Errorf("Unsupport proxy type: %s", proxyType)
}
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

1
go.mod
View File

@ -7,6 +7,7 @@ require (
github.com/go-chi/cors v1.0.0
github.com/go-chi/render v1.0.1
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/maxminddb-golang v1.3.0 // indirect
github.com/sirupsen/logrus v1.1.0

2
go.sum
View File

@ -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/gofrs/uuid v3.1.0+incompatible h1:q2rtkjaKT4YEr6E1kamy0Ha4RtepWlQBedyHx0uzKwA=
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/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/oschwald/geoip2-golang v1.2.1 h1:3iz+jmeJc6fuCyWeKgtXSXu7+zvkxJbHFXkMT5FVebU=

View File

@ -1,8 +1,10 @@
package hub
import (
"context"
"fmt"
"net/http"
"net/url"
"strconv"
"time"
@ -16,12 +18,24 @@ import (
func proxyRouter() http.Handler {
r := chi.NewRouter()
r.Get("/", getProxies)
r.Get("/{name}", getProxy)
r.Get("/{name}/delay", getProxyDelay)
r.Put("/{name}", updateProxy)
r.With(parseProxyName).Get("/{name}", getProxy)
r.With(parseProxyName).Get("/{name}/delay", getProxyDelay)
r.With(parseProxyName).Put("/{name}", updateProxy)
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 string `json:"type"`
}
@ -83,7 +97,7 @@ func getProxies(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()
proxy, exist := proxies[name]
if !exist {
@ -110,7 +124,7 @@ func updateProxy(w http.ResponseWriter, r *http.Request) {
return
}
name := chi.URLParam(r, "name")
name := r.Context().Value(contextKey("proxy name")).(string)
proxies := cfg.Proxies()
proxy, exist := proxies[name]
if !exist {
@ -162,7 +176,7 @@ func getProxyDelay(w http.ResponseWriter, r *http.Request) {
return
}
name := chi.URLParam(r, "name")
name := r.Context().Value(contextKey("proxy name")).(string)
proxies := cfg.Proxies()
proxy, exist := proxies[name]
if !exist {

View File

@ -15,8 +15,8 @@ func ruleRouter() http.Handler {
}
type Rule struct {
Name string `json:"name"`
Payload string `json:"type"`
Type string `json:"type"`
Payload string `json:"payload"`
Proxy string `json:"proxy"`
}
@ -30,7 +30,7 @@ func getRules(w http.ResponseWriter, r *http.Request) {
var rules []Rule
for _, rule := range rawRules {
rules = append(rules, Rule{
Name: rule.RuleType().String(),
Type: rule.RuleType().String(),
Payload: rule.Payload(),
Proxy: rule.Adapter(),
})

View File

@ -100,6 +100,12 @@ func authentication(next http.Handler) http.Handler {
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) {
w.WriteHeader(http.StatusOK)

View File

@ -5,12 +5,22 @@ import (
"io"
"net"
"net/http"
"sync"
"time"
"github.com/Dreamacro/clash/adapters/inbound"
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) {
conn := newTrafficTrack(proxy.Conn(), t.traffic)
req := request.R
@ -66,12 +76,16 @@ func relay(leftConn, rightConn net.Conn) {
ch := make(chan error)
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())
ch <- err
}()
io.Copy(rightConn, leftConn)
buf := bufPool.Get().([]byte)
io.CopyBuffer(rightConn, leftConn, buf)
bufPool.Put(buf[:cap(buf)])
rightConn.SetReadDeadline(time.Now())
<-ch
}