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
|
||||
|
||||
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"]
|
||||
|
19
README.md
19
README.md
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
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
|
||||
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
1
go.mod
@ -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
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/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=
|
||||
|
@ -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 {
|
||||
|
@ -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(),
|
||||
})
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
Reference in New Issue
Block a user