Compare commits
80 Commits
Author | SHA1 | Date | |
---|---|---|---|
dff1e8f1ce | |||
995aa7a8fc | |||
3ca5d17c40 | |||
244cb370a4 | |||
c35cb24bda | |||
b6ff08074c | |||
70d53fd45a | |||
f231a63e93 | |||
6091fcdfec | |||
bcfc15e398 | |||
045edc188c | |||
0778591524 | |||
d5e52bed43 | |||
06fdd3abe0 | |||
4e5898197a | |||
f96ebab99f | |||
3c54f99fea | |||
824f5bd731 | |||
3f3db8476e | |||
f375f080da | |||
e19e9ef5a4 | |||
682e65cb54 | |||
16a6d409d9 | |||
4186bcf1b2 | |||
df5112175f | |||
d9341a49ea | |||
4e9e4b6cde | |||
936b7012ba | |||
a9cbd9ec98 | |||
c9943fb857 | |||
a40274e2a2 | |||
b59d45c660 | |||
7b01e103c2 | |||
93a8acecce | |||
586bb91c0c | |||
baf03b81e3 | |||
9807e1189c | |||
3d5a0d9f73 | |||
cc96187f58 | |||
3aefa1d924 | |||
42e21b3733 | |||
0a35237915 | |||
a1f3a5ea26 | |||
e63f995258 | |||
d0c829c578 | |||
4ad9761b32 | |||
1f593d37fb | |||
109bfcb0f9 | |||
7ee49f5171 | |||
d759d16944 | |||
807d53c1e7 | |||
1355196b7c | |||
573316bcde | |||
784c28266c | |||
5da1b2a8aa | |||
0976d27cb1 | |||
6c83ff3496 | |||
f7f97ef625 | |||
5acdd72a1d | |||
f53686103d | |||
f63c9eb22f | |||
a37243cf30 | |||
b3c1b4a840 | |||
14bbf6eedc | |||
aa81193d5b | |||
9eb98e399d | |||
d48cfecf60 | |||
6036fb63ba | |||
cd48f69b1f | |||
fcc594ae26 | |||
f4de055aa1 | |||
35925cb3da | |||
ff430df845 | |||
e4cdea2111 | |||
b6ee47a541 | |||
b25009cde7 | |||
6fedd7ec84 | |||
9619c3fb20 | |||
02d029dd2d | |||
09c28e0355 |
30
.github/workflows/codeql-analysis.yml
vendored
Normal file
30
.github/workflows/codeql-analysis.yml
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master, dev ]
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'go' ]
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
5
.github/workflows/go.yml
vendored
5
.github/workflows/go.yml
vendored
@ -9,7 +9,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.15.x
|
||||
go-version: 1.16
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
@ -26,7 +26,7 @@ jobs:
|
||||
run: |
|
||||
go test ./...
|
||||
go vet ./...
|
||||
go get -u honnef.co/go/tools/cmd/staticcheck
|
||||
go install honnef.co/go/tools/cmd/staticcheck@latest
|
||||
staticcheck -- $(go list ./...)
|
||||
|
||||
- name: Build
|
||||
@ -44,4 +44,3 @@ jobs:
|
||||
with:
|
||||
files: bin/*
|
||||
draft: true
|
||||
prerelease: true
|
||||
|
@ -10,7 +10,7 @@ RUN go mod download && \
|
||||
mv ./bin/clash-docker /clash
|
||||
|
||||
FROM alpine:latest
|
||||
LABEL org.opencontainers.image.source https://github.com/Dreamacro/clash
|
||||
LABEL org.opencontainers.image.source="https://github.com/Dreamacro/clash"
|
||||
|
||||
RUN apk add --no-cache ca-certificates
|
||||
COPY --from=builder /Country.mmdb /root/.config/clash/
|
||||
|
10
Makefile
10
Makefile
@ -8,6 +8,7 @@ GOBUILD=CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/Dreamacro/clas
|
||||
|
||||
PLATFORM_LIST = \
|
||||
darwin-amd64 \
|
||||
darwin-arm64 \
|
||||
linux-386 \
|
||||
linux-amd64 \
|
||||
linux-armv5 \
|
||||
@ -21,7 +22,8 @@ PLATFORM_LIST = \
|
||||
linux-mips64 \
|
||||
linux-mips64le \
|
||||
freebsd-386 \
|
||||
freebsd-amd64
|
||||
freebsd-amd64 \
|
||||
freebsd-arm64
|
||||
|
||||
WINDOWS_ARCH_LIST = \
|
||||
windows-386 \
|
||||
@ -36,6 +38,9 @@ docker:
|
||||
darwin-amd64:
|
||||
GOARCH=amd64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
darwin-arm64:
|
||||
GOARCH=arm64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
linux-386:
|
||||
GOARCH=386 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
@ -78,6 +83,9 @@ freebsd-386:
|
||||
freebsd-amd64:
|
||||
GOARCH=amd64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
freebsd-arm64:
|
||||
GOARCH=arm64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
windows-386:
|
||||
GOARCH=386 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
||||
|
||||
|
@ -40,6 +40,9 @@ Documentations are now moved to [GitHub Wiki](https://github.com/Dreamacro/clash
|
||||
## Premium Release
|
||||
[Release](https://github.com/Dreamacro/clash/releases/tag/premium)
|
||||
|
||||
## Development
|
||||
If you want to build an application that uses clash as a library, check out the the [GitHub Wiki](https://github.com/Dreamacro/clash/wiki/use-clash-as-a-library)
|
||||
|
||||
## Credits
|
||||
|
||||
* [riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2)
|
||||
@ -57,4 +60,3 @@ This software is released under the GPL-3.0 license.
|
||||
- [x] Redir proxy
|
||||
- [x] UDP support
|
||||
- [x] Connection manager
|
||||
- [ ] ~~Event API~~
|
||||
|
@ -1,11 +1,12 @@
|
||||
package outbound
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/queue"
|
||||
@ -14,101 +15,25 @@ import (
|
||||
"go.uber.org/atomic"
|
||||
)
|
||||
|
||||
type Base struct {
|
||||
name string
|
||||
addr string
|
||||
tp C.AdapterType
|
||||
udp bool
|
||||
}
|
||||
|
||||
func (b *Base) Name() string {
|
||||
return b.name
|
||||
}
|
||||
|
||||
func (b *Base) Type() C.AdapterType {
|
||||
return b.tp
|
||||
}
|
||||
|
||||
func (b *Base) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
return c, errors.New("no support")
|
||||
}
|
||||
|
||||
func (b *Base) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
return nil, errors.New("no support")
|
||||
}
|
||||
|
||||
func (b *Base) SupportUDP() bool {
|
||||
return b.udp
|
||||
}
|
||||
|
||||
func (b *Base) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(map[string]string{
|
||||
"type": b.Type().String(),
|
||||
})
|
||||
}
|
||||
|
||||
func (b *Base) Addr() string {
|
||||
return b.addr
|
||||
}
|
||||
|
||||
func (b *Base) Unwrap(metadata *C.Metadata) C.Proxy {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewBase(name string, addr string, tp C.AdapterType, udp bool) *Base {
|
||||
return &Base{name, addr, tp, udp}
|
||||
}
|
||||
|
||||
type conn struct {
|
||||
net.Conn
|
||||
chain C.Chain
|
||||
}
|
||||
|
||||
func (c *conn) Chains() C.Chain {
|
||||
return c.chain
|
||||
}
|
||||
|
||||
func (c *conn) AppendToChains(a C.ProxyAdapter) {
|
||||
c.chain = append(c.chain, a.Name())
|
||||
}
|
||||
|
||||
func NewConn(c net.Conn, a C.ProxyAdapter) C.Conn {
|
||||
return &conn{c, []string{a.Name()}}
|
||||
}
|
||||
|
||||
type packetConn struct {
|
||||
net.PacketConn
|
||||
chain C.Chain
|
||||
}
|
||||
|
||||
func (c *packetConn) Chains() C.Chain {
|
||||
return c.chain
|
||||
}
|
||||
|
||||
func (c *packetConn) AppendToChains(a C.ProxyAdapter) {
|
||||
c.chain = append(c.chain, a.Name())
|
||||
}
|
||||
|
||||
func newPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn {
|
||||
return &packetConn{pc, []string{a.Name()}}
|
||||
}
|
||||
|
||||
type Proxy struct {
|
||||
C.ProxyAdapter
|
||||
history *queue.Queue
|
||||
alive *atomic.Bool
|
||||
}
|
||||
|
||||
// Alive implements C.Proxy
|
||||
func (p *Proxy) Alive() bool {
|
||||
return p.alive.Load()
|
||||
}
|
||||
|
||||
// Dial implements C.Proxy
|
||||
func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout)
|
||||
defer cancel()
|
||||
return p.DialContext(ctx, metadata)
|
||||
}
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||
conn, err := p.ProxyAdapter.DialContext(ctx, metadata)
|
||||
if err != nil {
|
||||
@ -117,6 +42,7 @@ func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
|
||||
return conn, err
|
||||
}
|
||||
|
||||
// DelayHistory implements C.Proxy
|
||||
func (p *Proxy) DelayHistory() []C.DelayHistory {
|
||||
queue := p.history.Copy()
|
||||
histories := []C.DelayHistory{}
|
||||
@ -127,6 +53,7 @@ func (p *Proxy) DelayHistory() []C.DelayHistory {
|
||||
}
|
||||
|
||||
// LastDelay return last history record. if proxy is not alive, return the max value of uint16.
|
||||
// implements C.Proxy
|
||||
func (p *Proxy) LastDelay() (delay uint16) {
|
||||
var max uint16 = 0xffff
|
||||
if !p.alive.Load() {
|
||||
@ -144,6 +71,7 @@ func (p *Proxy) LastDelay() (delay uint16) {
|
||||
return history.Delay
|
||||
}
|
||||
|
||||
// MarshalJSON implements C.ProxyAdapter
|
||||
func (p *Proxy) MarshalJSON() ([]byte, error) {
|
||||
inner, err := p.ProxyAdapter.MarshalJSON()
|
||||
if err != nil {
|
||||
@ -154,10 +82,12 @@ func (p *Proxy) MarshalJSON() ([]byte, error) {
|
||||
json.Unmarshal(inner, &mapping)
|
||||
mapping["history"] = p.DelayHistory()
|
||||
mapping["name"] = p.Name()
|
||||
mapping["udp"] = p.SupportUDP()
|
||||
return json.Marshal(mapping)
|
||||
}
|
||||
|
||||
// URLTest get the delay for the specified URL
|
||||
// implements C.Proxy
|
||||
func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
|
||||
defer func() {
|
||||
p.alive.Store(err == nil)
|
||||
@ -218,3 +148,31 @@ func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
|
||||
func NewProxy(adapter C.ProxyAdapter) *Proxy {
|
||||
return &Proxy{adapter, queue.New(10), atomic.NewBool(true)}
|
||||
}
|
||||
|
||||
func urlToMetadata(rawURL string) (addr C.Metadata, err error) {
|
||||
u, err := url.Parse(rawURL)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
port := u.Port()
|
||||
if port == "" {
|
||||
switch u.Scheme {
|
||||
case "https":
|
||||
port = "443"
|
||||
case "http":
|
||||
port = "80"
|
||||
default:
|
||||
err = fmt.Errorf("%s scheme not Support", rawURL)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
addr = C.Metadata{
|
||||
AddrType: C.AtypDomainName,
|
||||
Host: u.Hostname(),
|
||||
DstIP: nil,
|
||||
DstPort: port,
|
||||
}
|
||||
return
|
||||
}
|
21
adapter/inbound/http.go
Normal file
21
adapter/inbound/http.go
Normal file
@ -0,0 +1,21 @@
|
||||
package inbound
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/context"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
)
|
||||
|
||||
// NewHTTP receive normal http request and return HTTPContext
|
||||
func NewHTTP(target string, source net.Addr, conn net.Conn) *context.ConnContext {
|
||||
metadata := parseSocksAddr(socks5.ParseAddr(target))
|
||||
metadata.NetWork = C.TCP
|
||||
metadata.Type = C.HTTP
|
||||
if ip, port, err := parseAddr(source.String()); err == nil {
|
||||
metadata.SrcIP = ip
|
||||
metadata.SrcPort = port
|
||||
}
|
||||
return context.NewConnContext(conn, metadata)
|
||||
}
|
@ -5,18 +5,16 @@ import (
|
||||
"net/http"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/context"
|
||||
)
|
||||
|
||||
// NewHTTPS is HTTPAdapter generator
|
||||
func NewHTTPS(request *http.Request, conn net.Conn) *SocketAdapter {
|
||||
// NewHTTPS receive CONNECT request and return ConnContext
|
||||
func NewHTTPS(request *http.Request, conn net.Conn) *context.ConnContext {
|
||||
metadata := parseHTTPAddr(request)
|
||||
metadata.Type = C.HTTPCONNECT
|
||||
if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil {
|
||||
metadata.SrcIP = ip
|
||||
metadata.SrcPort = port
|
||||
}
|
||||
return &SocketAdapter{
|
||||
metadata: metadata,
|
||||
Conn: conn,
|
||||
}
|
||||
return context.NewConnContext(conn, metadata)
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
package inbound
|
||||
|
||||
import (
|
||||
"github.com/Dreamacro/clash/component/socks5"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
)
|
||||
|
||||
// PacketAdapter is a UDP Packet adapter for socks/redir/tun
|
22
adapter/inbound/socket.go
Normal file
22
adapter/inbound/socket.go
Normal file
@ -0,0 +1,22 @@
|
||||
package inbound
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/context"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
)
|
||||
|
||||
// NewSocket receive TCP inbound and return ConnContext
|
||||
func NewSocket(target socks5.Addr, conn net.Conn, source C.Type) *context.ConnContext {
|
||||
metadata := parseSocksAddr(target)
|
||||
metadata.NetWork = C.TCP
|
||||
metadata.Type = source
|
||||
if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil {
|
||||
metadata.SrcIP = ip
|
||||
metadata.SrcPort = port
|
||||
}
|
||||
|
||||
return context.NewConnContext(conn, metadata)
|
||||
}
|
@ -6,8 +6,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Dreamacro/clash/component/socks5"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
)
|
||||
|
||||
func parseSocksAddr(target socks5.Addr) *C.Metadata {
|
100
adapter/outbound/base.go
Normal file
100
adapter/outbound/base.go
Normal file
@ -0,0 +1,100 @@
|
||||
package outbound
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
|
||||
type Base struct {
|
||||
name string
|
||||
addr string
|
||||
tp C.AdapterType
|
||||
udp bool
|
||||
}
|
||||
|
||||
// Name implements C.ProxyAdapter
|
||||
func (b *Base) Name() string {
|
||||
return b.name
|
||||
}
|
||||
|
||||
// Type implements C.ProxyAdapter
|
||||
func (b *Base) Type() C.AdapterType {
|
||||
return b.tp
|
||||
}
|
||||
|
||||
// StreamConn implements C.ProxyAdapter
|
||||
func (b *Base) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
return c, errors.New("no support")
|
||||
}
|
||||
|
||||
// DialUDP implements C.ProxyAdapter
|
||||
func (b *Base) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
return nil, errors.New("no support")
|
||||
}
|
||||
|
||||
// SupportUDP implements C.ProxyAdapter
|
||||
func (b *Base) SupportUDP() bool {
|
||||
return b.udp
|
||||
}
|
||||
|
||||
// MarshalJSON implements C.ProxyAdapter
|
||||
func (b *Base) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(map[string]string{
|
||||
"type": b.Type().String(),
|
||||
})
|
||||
}
|
||||
|
||||
// Addr implements C.ProxyAdapter
|
||||
func (b *Base) Addr() string {
|
||||
return b.addr
|
||||
}
|
||||
|
||||
// Unwrap implements C.ProxyAdapter
|
||||
func (b *Base) Unwrap(metadata *C.Metadata) C.Proxy {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewBase(name string, addr string, tp C.AdapterType, udp bool) *Base {
|
||||
return &Base{name, addr, tp, udp}
|
||||
}
|
||||
|
||||
type conn struct {
|
||||
net.Conn
|
||||
chain C.Chain
|
||||
}
|
||||
|
||||
// Chains implements C.Connection
|
||||
func (c *conn) Chains() C.Chain {
|
||||
return c.chain
|
||||
}
|
||||
|
||||
// AppendToChains implements C.Connection
|
||||
func (c *conn) AppendToChains(a C.ProxyAdapter) {
|
||||
c.chain = append(c.chain, a.Name())
|
||||
}
|
||||
|
||||
func NewConn(c net.Conn, a C.ProxyAdapter) C.Conn {
|
||||
return &conn{c, []string{a.Name()}}
|
||||
}
|
||||
|
||||
type packetConn struct {
|
||||
net.PacketConn
|
||||
chain C.Chain
|
||||
}
|
||||
|
||||
// Chains implements C.Connection
|
||||
func (c *packetConn) Chains() C.Chain {
|
||||
return c.chain
|
||||
}
|
||||
|
||||
// AppendToChains implements C.Connection
|
||||
func (c *packetConn) AppendToChains(a C.ProxyAdapter) {
|
||||
c.chain = append(c.chain, a.Name())
|
||||
}
|
||||
|
||||
func newPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn {
|
||||
return &packetConn{pc, []string{a.Name()}}
|
||||
}
|
@ -12,6 +12,7 @@ type Direct struct {
|
||||
*Base
|
||||
}
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||
address := net.JoinHostPort(metadata.String(), metadata.DstPort)
|
||||
|
||||
@ -23,6 +24,7 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
|
||||
return NewConn(c, d), nil
|
||||
}
|
||||
|
||||
// DialUDP implements C.ProxyAdapter
|
||||
func (d *Direct) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
pc, err := dialer.ListenPacket("udp", "")
|
||||
if err != nil {
|
@ -35,6 +35,7 @@ type HttpOption struct {
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
}
|
||||
|
||||
// StreamConn implements C.ProxyAdapter
|
||||
func (h *Http) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
if h.tlsConfig != nil {
|
||||
cc := tls.Client(c, h.tlsConfig)
|
||||
@ -51,13 +52,16 @@ func (h *Http) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
|
||||
c, err := dialer.DialContext(ctx, "tcp", h.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
|
||||
defer safeConnClose(c, err)
|
||||
|
||||
c, err = h.StreamConn(c, metadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -121,7 +125,6 @@ func NewHttp(option HttpOption) *Http {
|
||||
}
|
||||
tlsConfig = &tls.Config{
|
||||
InsecureSkipVerify: option.SkipCertVerify,
|
||||
ClientSessionCache: getClientSessionCache(),
|
||||
ServerName: sni,
|
||||
}
|
||||
}
|
@ -14,10 +14,12 @@ type Reject struct {
|
||||
*Base
|
||||
}
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||
return NewConn(&NopConn{}, r), nil
|
||||
}
|
||||
|
||||
// DialUDP implements C.ProxyAdapter
|
||||
func (r *Reject) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
return nil, errors.New("match reject rule")
|
||||
}
|
@ -2,7 +2,6 @@ package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
@ -10,10 +9,10 @@ import (
|
||||
|
||||
"github.com/Dreamacro/clash/common/structure"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
obfs "github.com/Dreamacro/clash/component/simple-obfs"
|
||||
"github.com/Dreamacro/clash/component/socks5"
|
||||
v2rayObfs "github.com/Dreamacro/clash/component/v2ray-plugin"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
obfs "github.com/Dreamacro/clash/transport/simple-obfs"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
v2rayObfs "github.com/Dreamacro/clash/transport/v2ray-plugin"
|
||||
|
||||
"github.com/Dreamacro/go-shadowsocks2/core"
|
||||
)
|
||||
@ -54,6 +53,7 @@ type v2rayObfsOption struct {
|
||||
Mux bool `obfs:"mux,omitempty"`
|
||||
}
|
||||
|
||||
// StreamConn implements C.ProxyAdapter
|
||||
func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
switch ss.obfsMode {
|
||||
case "tls":
|
||||
@ -73,17 +73,21 @@ func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, e
|
||||
return c, err
|
||||
}
|
||||
|
||||
func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
|
||||
c, err := dialer.DialContext(ctx, "tcp", ss.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
|
||||
defer safeConnClose(c, err)
|
||||
|
||||
c, err = ss.StreamConn(c, metadata)
|
||||
return NewConn(c, ss), err
|
||||
}
|
||||
|
||||
// DialUDP implements C.ProxyAdapter
|
||||
func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
pc, err := dialer.ListenPacket("udp", "")
|
||||
if err != nil {
|
||||
@ -92,6 +96,7 @@ func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
|
||||
addr, err := resolveUDPAddr("udp", ss.addr)
|
||||
if err != nil {
|
||||
pc.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -99,12 +104,6 @@ func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ss), nil
|
||||
}
|
||||
|
||||
func (ss *ShadowSocks) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(map[string]string{
|
||||
"type": ss.Type().String(),
|
||||
})
|
||||
}
|
||||
|
||||
func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||
cipher := option.Cipher
|
||||
@ -150,7 +149,6 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||
if opts.TLS {
|
||||
v2rayOption.TLS = true
|
||||
v2rayOption.SkipCertVerify = opts.SkipCertVerify
|
||||
v2rayOption.SessionCache = getClientSessionCache()
|
||||
}
|
||||
}
|
||||
|
@ -2,22 +2,23 @@ package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/ssr/obfs"
|
||||
"github.com/Dreamacro/clash/component/ssr/protocol"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/transport/ssr/obfs"
|
||||
"github.com/Dreamacro/clash/transport/ssr/protocol"
|
||||
|
||||
"github.com/Dreamacro/go-shadowsocks2/core"
|
||||
"github.com/Dreamacro/go-shadowsocks2/shadowaead"
|
||||
"github.com/Dreamacro/go-shadowsocks2/shadowstream"
|
||||
)
|
||||
|
||||
type ShadowSocksR struct {
|
||||
*Base
|
||||
cipher *core.StreamCipher
|
||||
cipher core.Cipher
|
||||
obfs obfs.Obfs
|
||||
protocol protocol.Protocol
|
||||
}
|
||||
@ -35,33 +36,43 @@ type ShadowSocksROption struct {
|
||||
UDP bool `proxy:"udp,omitempty"`
|
||||
}
|
||||
|
||||
// StreamConn implements C.ProxyAdapter
|
||||
func (ssr *ShadowSocksR) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
c = obfs.NewConn(c, ssr.obfs)
|
||||
c = ssr.obfs.StreamConn(c)
|
||||
c = ssr.cipher.StreamConn(c)
|
||||
conn, ok := c.(*shadowstream.Conn)
|
||||
if !ok {
|
||||
var (
|
||||
iv []byte
|
||||
err error
|
||||
)
|
||||
switch conn := c.(type) {
|
||||
case *shadowstream.Conn:
|
||||
iv, err = conn.ObtainWriteIV()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case *shadowaead.Conn:
|
||||
return nil, fmt.Errorf("invalid connection type")
|
||||
}
|
||||
iv, err := conn.ObtainWriteIV()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c = protocol.NewConn(c, ssr.protocol, iv)
|
||||
c = ssr.protocol.StreamConn(c, iv)
|
||||
_, err = c.Write(serializesSocksAddr(metadata))
|
||||
return c, err
|
||||
}
|
||||
|
||||
func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
|
||||
c, err := dialer.DialContext(ctx, "tcp", ssr.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", ssr.addr, err)
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
|
||||
defer safeConnClose(c, err)
|
||||
|
||||
c, err = ssr.StreamConn(c, metadata)
|
||||
return NewConn(c, ssr), err
|
||||
}
|
||||
|
||||
// DialUDP implements C.ProxyAdapter
|
||||
func (ssr *ShadowSocksR) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
pc, err := dialer.ListenPacket("udp", "")
|
||||
if err != nil {
|
||||
@ -70,55 +81,58 @@ func (ssr *ShadowSocksR) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
|
||||
addr, err := resolveUDPAddr("udp", ssr.addr)
|
||||
if err != nil {
|
||||
pc.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pc = ssr.cipher.PacketConn(pc)
|
||||
pc = protocol.NewPacketConn(pc, ssr.protocol)
|
||||
pc = ssr.protocol.PacketConn(pc)
|
||||
return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ssr), nil
|
||||
}
|
||||
|
||||
func (ssr *ShadowSocksR) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(map[string]string{
|
||||
"type": ssr.Type().String(),
|
||||
})
|
||||
}
|
||||
|
||||
func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
|
||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||
cipher := option.Cipher
|
||||
password := option.Password
|
||||
coreCiph, err := core.PickCipher(cipher, nil, password)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ssr %s initialize cipher error: %w", addr, err)
|
||||
return nil, fmt.Errorf("ssr %s initialize error: %w", addr, err)
|
||||
}
|
||||
ciph, ok := coreCiph.(*core.StreamCipher)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%s is not a supported stream cipher in ssr", cipher)
|
||||
var (
|
||||
ivSize int
|
||||
key []byte
|
||||
)
|
||||
if option.Cipher == "dummy" {
|
||||
ivSize = 0
|
||||
key = core.Kdf(option.Password, 16)
|
||||
} else {
|
||||
ciph, ok := coreCiph.(*core.StreamCipher)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%s is not dummy or a supported stream cipher in ssr", cipher)
|
||||
}
|
||||
ivSize = ciph.IVSize()
|
||||
key = ciph.Key
|
||||
}
|
||||
|
||||
obfs, err := obfs.PickObfs(option.Obfs, &obfs.Base{
|
||||
IVSize: ciph.IVSize(),
|
||||
Key: ciph.Key,
|
||||
HeadLen: 30,
|
||||
Host: option.Server,
|
||||
Port: option.Port,
|
||||
Param: option.ObfsParam,
|
||||
obfs, obfsOverhead, err := obfs.PickObfs(option.Obfs, &obfs.Base{
|
||||
Host: option.Server,
|
||||
Port: option.Port,
|
||||
Key: key,
|
||||
IVSize: ivSize,
|
||||
Param: option.ObfsParam,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ssr %s initialize obfs error: %w", addr, err)
|
||||
}
|
||||
|
||||
protocol, err := protocol.PickProtocol(option.Protocol, &protocol.Base{
|
||||
IV: nil,
|
||||
Key: ciph.Key,
|
||||
TCPMss: 1460,
|
||||
Param: option.ProtocolParam,
|
||||
Key: key,
|
||||
Overhead: obfsOverhead,
|
||||
Param: option.ProtocolParam,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ssr %s initialize protocol error: %w", addr, err)
|
||||
}
|
||||
protocol.SetOverhead(obfs.GetObfsOverhead() + protocol.GetProtocolOverhead())
|
||||
|
||||
return &ShadowSocksR{
|
||||
Base: &Base{
|
||||
@ -127,7 +141,7 @@ func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
|
||||
tp: C.ShadowsocksR,
|
||||
udp: option.UDP,
|
||||
},
|
||||
cipher: ciph,
|
||||
cipher: coreCiph,
|
||||
obfs: obfs,
|
||||
protocol: protocol,
|
||||
}, nil
|
@ -8,9 +8,9 @@ import (
|
||||
|
||||
"github.com/Dreamacro/clash/common/structure"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
obfs "github.com/Dreamacro/clash/component/simple-obfs"
|
||||
"github.com/Dreamacro/clash/component/snell"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
obfs "github.com/Dreamacro/clash/transport/simple-obfs"
|
||||
"github.com/Dreamacro/clash/transport/snell"
|
||||
)
|
||||
|
||||
type Snell struct {
|
||||
@ -48,6 +48,7 @@ func streamConn(c net.Conn, option streamOption) *snell.Snell {
|
||||
return snell.StreamConn(c, option.psk, option.version)
|
||||
}
|
||||
|
||||
// StreamConn implements C.ProxyAdapter
|
||||
func (s *Snell) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
|
||||
port, _ := strconv.Atoi(metadata.DstPort)
|
||||
@ -55,7 +56,8 @@ func (s *Snell) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
return c, err
|
||||
}
|
||||
|
||||
func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
|
||||
if s.version == snell.Version2 {
|
||||
c, err := s.pool.Get()
|
||||
if err != nil {
|
||||
@ -63,7 +65,10 @@ func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
|
||||
}
|
||||
|
||||
port, _ := strconv.Atoi(metadata.DstPort)
|
||||
err = snell.WriteHeader(c, metadata.String(), uint(port), s.version)
|
||||
if err = snell.WriteHeader(c, metadata.String(), uint(port), s.version); err != nil {
|
||||
c.Close()
|
||||
return nil, err
|
||||
}
|
||||
return NewConn(c, s), err
|
||||
}
|
||||
|
||||
@ -73,6 +78,8 @@ func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
|
||||
defer safeConnClose(c, err)
|
||||
|
||||
c, err = s.StreamConn(c, metadata)
|
||||
return NewConn(c, s), err
|
||||
}
|
@ -11,8 +11,8 @@ import (
|
||||
"strconv"
|
||||
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/socks5"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
)
|
||||
|
||||
type Socks5 struct {
|
||||
@ -35,6 +35,7 @@ type Socks5Option struct {
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
}
|
||||
|
||||
// StreamConn implements C.ProxyAdapter
|
||||
func (ss *Socks5) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
if ss.tls {
|
||||
cc := tls.Client(c, ss.tlsConfig)
|
||||
@ -58,13 +59,16 @@ func (ss *Socks5) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
|
||||
c, err := dialer.DialContext(ctx, "tcp", ss.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
|
||||
defer safeConnClose(c, err)
|
||||
|
||||
c, err = ss.StreamConn(c, metadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -73,8 +77,9 @@ func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn
|
||||
return NewConn(c, ss), nil
|
||||
}
|
||||
|
||||
// DialUDP implements C.ProxyAdapter
|
||||
func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout)
|
||||
defer cancel()
|
||||
c, err := dialer.DialContext(ctx, "tcp", ss.addr)
|
||||
if err != nil {
|
||||
@ -88,11 +93,7 @@ func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||
c = cc
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
c.Close()
|
||||
}
|
||||
}()
|
||||
defer safeConnClose(c, err)
|
||||
|
||||
tcpKeepAlive(c)
|
||||
var user *socks5.User
|
||||
@ -122,7 +123,21 @@ func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||
pc.Close()
|
||||
}()
|
||||
|
||||
return newPacketConn(&socksPacketConn{PacketConn: pc, rAddr: bindAddr.UDPAddr(), tcpConn: c}, ss), nil
|
||||
// Support unspecified UDP bind address.
|
||||
bindUDPAddr := bindAddr.UDPAddr()
|
||||
if bindUDPAddr == nil {
|
||||
err = errors.New("invalid UDP bind address")
|
||||
return
|
||||
} else if bindUDPAddr.IP.IsUnspecified() {
|
||||
serverAddr, err := resolveUDPAddr("udp", ss.Addr())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bindUDPAddr.IP = serverAddr.IP
|
||||
}
|
||||
|
||||
return newPacketConn(&socksPacketConn{PacketConn: pc, rAddr: bindUDPAddr, tcpConn: c}, ss), nil
|
||||
}
|
||||
|
||||
func NewSocks5(option Socks5Option) *Socks5 {
|
||||
@ -130,7 +145,6 @@ func NewSocks5(option Socks5Option) *Socks5 {
|
||||
if option.TLS {
|
||||
tlsConfig = &tls.Config{
|
||||
InsecureSkipVerify: option.SkipCertVerify,
|
||||
ClientSessionCache: getClientSessionCache(),
|
||||
ServerName: option.Server,
|
||||
}
|
||||
}
|
176
adapter/outbound/trojan.go
Normal file
176
adapter/outbound/trojan.go
Normal file
@ -0,0 +1,176 @@
|
||||
package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/transport/gun"
|
||||
"github.com/Dreamacro/clash/transport/trojan"
|
||||
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
type Trojan struct {
|
||||
*Base
|
||||
instance *trojan.Trojan
|
||||
|
||||
// for gun mux
|
||||
gunTLSConfig *tls.Config
|
||||
gunConfig *gun.Config
|
||||
transport *http2.Transport
|
||||
}
|
||||
|
||||
type TrojanOption struct {
|
||||
Name string `proxy:"name"`
|
||||
Server string `proxy:"server"`
|
||||
Port int `proxy:"port"`
|
||||
Password string `proxy:"password"`
|
||||
ALPN []string `proxy:"alpn,omitempty"`
|
||||
SNI string `proxy:"sni,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
UDP bool `proxy:"udp,omitempty"`
|
||||
Network string `proxy:"network,omitempty"`
|
||||
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
||||
}
|
||||
|
||||
// StreamConn implements C.ProxyAdapter
|
||||
func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
var err error
|
||||
if t.transport != nil {
|
||||
c, err = gun.StreamGunWithConn(c, t.gunTLSConfig, t.gunConfig)
|
||||
} else {
|
||||
c, err = t.instance.StreamConn(c)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||
}
|
||||
|
||||
err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata))
|
||||
return c, err
|
||||
}
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
|
||||
// gun transport
|
||||
if t.transport != nil {
|
||||
c, err := gun.StreamGunWithTransport(t.transport, t.gunConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata)); err != nil {
|
||||
c.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewConn(c, t), nil
|
||||
}
|
||||
|
||||
c, err := dialer.DialContext(ctx, "tcp", t.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
|
||||
defer safeConnClose(c, err)
|
||||
|
||||
c, err = t.StreamConn(c, metadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewConn(c, t), err
|
||||
}
|
||||
|
||||
// DialUDP implements C.ProxyAdapter
|
||||
func (t *Trojan) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||
var c net.Conn
|
||||
|
||||
// grpc transport
|
||||
if t.transport != nil {
|
||||
c, err = gun.StreamGunWithTransport(t.transport, t.gunConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||
}
|
||||
defer safeConnClose(c, err)
|
||||
} else {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout)
|
||||
defer cancel()
|
||||
c, err = dialer.DialContext(ctx, "tcp", t.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||
}
|
||||
defer safeConnClose(c, err)
|
||||
tcpKeepAlive(c)
|
||||
c, err = t.instance.StreamConn(c)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||
}
|
||||
}
|
||||
|
||||
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pc := t.instance.PacketConn(c)
|
||||
return newPacketConn(pc, t), err
|
||||
}
|
||||
|
||||
func NewTrojan(option TrojanOption) (*Trojan, error) {
|
||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||
|
||||
tOption := &trojan.Option{
|
||||
Password: option.Password,
|
||||
ALPN: option.ALPN,
|
||||
ServerName: option.Server,
|
||||
SkipCertVerify: option.SkipCertVerify,
|
||||
}
|
||||
|
||||
if option.SNI != "" {
|
||||
tOption.ServerName = option.SNI
|
||||
}
|
||||
|
||||
t := &Trojan{
|
||||
Base: &Base{
|
||||
name: option.Name,
|
||||
addr: addr,
|
||||
tp: C.Trojan,
|
||||
udp: option.UDP,
|
||||
},
|
||||
instance: trojan.New(tOption),
|
||||
}
|
||||
|
||||
if option.Network == "grpc" {
|
||||
dialFn := func(network, addr string) (net.Conn, error) {
|
||||
c, err := dialer.DialContext(context.Background(), "tcp", t.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", t.addr, err.Error())
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
NextProtos: option.ALPN,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
InsecureSkipVerify: tOption.SkipCertVerify,
|
||||
ServerName: tOption.ServerName,
|
||||
}
|
||||
|
||||
t.transport = gun.NewHTTP2Client(dialFn, tlsConfig)
|
||||
t.gunTLSConfig = tlsConfig
|
||||
t.gunConfig = &gun.Config{
|
||||
ServiceName: option.GrpcOpts.GrpcServiceName,
|
||||
Host: tOption.ServerName,
|
||||
}
|
||||
}
|
||||
|
||||
return t, nil
|
||||
}
|
@ -2,56 +2,15 @@ package outbound
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
"github.com/Dreamacro/clash/component/socks5"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
)
|
||||
|
||||
const (
|
||||
tcpTimeout = 5 * time.Second
|
||||
)
|
||||
|
||||
var (
|
||||
globalClientSessionCache tls.ClientSessionCache
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
func urlToMetadata(rawURL string) (addr C.Metadata, err error) {
|
||||
u, err := url.Parse(rawURL)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
port := u.Port()
|
||||
if port == "" {
|
||||
switch u.Scheme {
|
||||
case "https":
|
||||
port = "443"
|
||||
case "http":
|
||||
port = "80"
|
||||
default:
|
||||
err = fmt.Errorf("%s scheme not Support", rawURL)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
addr = C.Metadata{
|
||||
AddrType: C.AtypDomainName,
|
||||
Host: u.Hostname(),
|
||||
DstIP: nil,
|
||||
DstPort: port,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func tcpKeepAlive(c net.Conn) {
|
||||
if tcp, ok := c.(*net.TCPConn); ok {
|
||||
tcp.SetKeepAlive(true)
|
||||
@ -59,13 +18,6 @@ func tcpKeepAlive(c net.Conn) {
|
||||
}
|
||||
}
|
||||
|
||||
func getClientSessionCache() tls.ClientSessionCache {
|
||||
once.Do(func() {
|
||||
globalClientSessionCache = tls.NewLRUClientSessionCache(128)
|
||||
})
|
||||
return globalClientSessionCache
|
||||
}
|
||||
|
||||
func serializesSocksAddr(metadata *C.Metadata) []byte {
|
||||
var buf [][]byte
|
||||
aType := uint8(metadata.AddrType)
|
||||
@ -98,3 +50,9 @@ func resolveUDPAddr(network, address string) (*net.UDPAddr, error) {
|
||||
}
|
||||
return net.ResolveUDPAddr(network, net.JoinHostPort(ip.String(), port))
|
||||
}
|
||||
|
||||
func safeConnClose(c net.Conn, err error) {
|
||||
if err != nil {
|
||||
c.Close()
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
@ -11,14 +12,22 @@ import (
|
||||
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
"github.com/Dreamacro/clash/component/vmess"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/transport/gun"
|
||||
"github.com/Dreamacro/clash/transport/vmess"
|
||||
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
type Vmess struct {
|
||||
*Base
|
||||
client *vmess.Client
|
||||
option *VmessOption
|
||||
|
||||
// for gun mux
|
||||
gunTLSConfig *tls.Config
|
||||
gunConfig *gun.Config
|
||||
transport *http2.Transport
|
||||
}
|
||||
|
||||
type VmessOption struct {
|
||||
@ -33,6 +42,7 @@ type VmessOption struct {
|
||||
Network string `proxy:"network,omitempty"`
|
||||
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
||||
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
|
||||
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
||||
WSPath string `proxy:"ws-path,omitempty"`
|
||||
WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
@ -50,6 +60,11 @@ type HTTP2Options struct {
|
||||
Path string `proxy:"path,omitempty"`
|
||||
}
|
||||
|
||||
type GrpcOptions struct {
|
||||
GrpcServiceName string `proxy:"grpc-service-name,omitempty"`
|
||||
}
|
||||
|
||||
// StreamConn implements C.ProxyAdapter
|
||||
func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
var err error
|
||||
switch v.option.Network {
|
||||
@ -71,7 +86,6 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
|
||||
if v.option.TLS {
|
||||
wsOpts.TLS = true
|
||||
wsOpts.SessionCache = getClientSessionCache()
|
||||
wsOpts.SkipCertVerify = v.option.SkipCertVerify
|
||||
wsOpts.ServerName = v.option.ServerName
|
||||
}
|
||||
@ -83,7 +97,6 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
tlsOpts := &vmess.TLSConfig{
|
||||
Host: host,
|
||||
SkipCertVerify: v.option.SkipCertVerify,
|
||||
SessionCache: getClientSessionCache(),
|
||||
}
|
||||
|
||||
if v.option.ServerName != "" {
|
||||
@ -110,7 +123,6 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
tlsOpts := vmess.TLSConfig{
|
||||
Host: host,
|
||||
SkipCertVerify: v.option.SkipCertVerify,
|
||||
SessionCache: getClientSessionCache(),
|
||||
NextProtos: []string{"h2"},
|
||||
}
|
||||
|
||||
@ -129,6 +141,8 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
}
|
||||
|
||||
c, err = vmess.StreamH2Conn(c, h2Opts)
|
||||
case "grpc":
|
||||
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig)
|
||||
default:
|
||||
// handle TLS
|
||||
if v.option.TLS {
|
||||
@ -136,7 +150,6 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
tlsOpts := &vmess.TLSConfig{
|
||||
Host: host,
|
||||
SkipCertVerify: v.option.SkipCertVerify,
|
||||
SessionCache: getClientSessionCache(),
|
||||
}
|
||||
|
||||
if v.option.ServerName != "" {
|
||||
@ -154,19 +167,38 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
return v.client.StreamConn(c, parseVmessAddr(metadata))
|
||||
}
|
||||
|
||||
func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
|
||||
// gun transport
|
||||
if v.transport != nil {
|
||||
c, err := gun.StreamGunWithTransport(v.transport, v.gunConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer safeConnClose(c, err)
|
||||
|
||||
c, err = v.client.StreamConn(c, parseVmessAddr(metadata))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewConn(c, v), nil
|
||||
}
|
||||
|
||||
c, err := dialer.DialContext(ctx, "tcp", v.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
defer safeConnClose(c, err)
|
||||
|
||||
c, err = v.StreamConn(c, metadata)
|
||||
return NewConn(c, v), err
|
||||
}
|
||||
|
||||
func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
// vmess use stream-oriented udp, so clash needs a net.UDPAddr
|
||||
// DialUDP implements C.ProxyAdapter
|
||||
func (v *Vmess) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||
// vmess use stream-oriented udp with a special address, so we needs a net.UDPAddr
|
||||
if !metadata.Resolved() {
|
||||
ip, err := resolver.ResolveIP(metadata.Host)
|
||||
if err != nil {
|
||||
@ -175,17 +207,33 @@ func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
metadata.DstIP = ip
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
|
||||
defer cancel()
|
||||
c, err := dialer.DialContext(ctx, "tcp", v.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||
var c net.Conn
|
||||
// gun transport
|
||||
if v.transport != nil {
|
||||
c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer safeConnClose(c, err)
|
||||
|
||||
c, err = v.client.StreamConn(c, parseVmessAddr(metadata))
|
||||
} else {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout)
|
||||
defer cancel()
|
||||
c, err = dialer.DialContext(ctx, "tcp", v.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
defer safeConnClose(c, err)
|
||||
|
||||
c, err = v.StreamConn(c, metadata)
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
c, err = v.StreamConn(c, metadata)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new vmess client error: %v", err)
|
||||
}
|
||||
|
||||
return newPacketConn(&vmessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil
|
||||
}
|
||||
|
||||
@ -197,15 +245,20 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
||||
Security: security,
|
||||
HostName: option.Server,
|
||||
Port: strconv.Itoa(option.Port),
|
||||
IsAead: option.AlterID == 0,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if option.Network == "h2" && !option.TLS {
|
||||
return nil, fmt.Errorf("TLS must be true with h2 network")
|
||||
|
||||
switch option.Network {
|
||||
case "h2", "grpc":
|
||||
if !option.TLS {
|
||||
return nil, fmt.Errorf("TLS must be true with h2/grpc network")
|
||||
}
|
||||
}
|
||||
|
||||
return &Vmess{
|
||||
v := &Vmess{
|
||||
Base: &Base{
|
||||
name: option.Name,
|
||||
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
||||
@ -214,7 +267,44 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
||||
},
|
||||
client: client,
|
||||
option: &option,
|
||||
}, nil
|
||||
}
|
||||
|
||||
switch option.Network {
|
||||
case "h2":
|
||||
if len(option.HTTP2Opts.Host) == 0 {
|
||||
option.HTTP2Opts.Host = append(option.HTTP2Opts.Host, "www.example.com")
|
||||
}
|
||||
case "grpc":
|
||||
dialFn := func(network, addr string) (net.Conn, error) {
|
||||
c, err := dialer.DialContext(context.Background(), "tcp", v.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
gunConfig := &gun.Config{
|
||||
ServiceName: v.option.GrpcOpts.GrpcServiceName,
|
||||
Host: v.option.ServerName,
|
||||
}
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: v.option.SkipCertVerify,
|
||||
ServerName: v.option.ServerName,
|
||||
}
|
||||
|
||||
if v.option.ServerName == "" {
|
||||
host, _, _ := net.SplitHostPort(v.addr)
|
||||
tlsConfig.ServerName = host
|
||||
gunConfig.Host = host
|
||||
}
|
||||
|
||||
v.gunTLSConfig = tlsConfig
|
||||
v.gunConfig = gunConfig
|
||||
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig)
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func parseVmessAddr(metadata *C.Metadata) *vmess.DstAddr {
|
@ -3,7 +3,7 @@ package outboundgroup
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/adapters/provider"
|
||||
"github.com/Dreamacro/clash/adapter/provider"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
|
@ -4,8 +4,8 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/Dreamacro/clash/adapters/outbound"
|
||||
"github.com/Dreamacro/clash/adapters/provider"
|
||||
"github.com/Dreamacro/clash/adapter/outbound"
|
||||
"github.com/Dreamacro/clash/adapter/provider"
|
||||
"github.com/Dreamacro/clash/common/singledo"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
@ -22,6 +22,7 @@ func (f *Fallback) Now() string {
|
||||
return proxy.Name()
|
||||
}
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||
proxy := f.findAliveProxy(true)
|
||||
c, err := proxy.DialContext(ctx, metadata)
|
||||
@ -31,6 +32,7 @@ func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata) (C.Con
|
||||
return c, err
|
||||
}
|
||||
|
||||
// DialUDP implements C.ProxyAdapter
|
||||
func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
proxy := f.findAliveProxy(true)
|
||||
pc, err := proxy.DialUDP(metadata)
|
||||
@ -40,6 +42,7 @@ func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
return pc, err
|
||||
}
|
||||
|
||||
// SupportUDP implements C.ProxyAdapter
|
||||
func (f *Fallback) SupportUDP() bool {
|
||||
if f.disableUDP {
|
||||
return false
|
||||
@ -49,6 +52,7 @@ func (f *Fallback) SupportUDP() bool {
|
||||
return proxy.SupportUDP()
|
||||
}
|
||||
|
||||
// MarshalJSON implements C.ProxyAdapter
|
||||
func (f *Fallback) MarshalJSON() ([]byte, error) {
|
||||
var all []string
|
||||
for _, proxy := range f.proxies(false) {
|
||||
@ -61,6 +65,7 @@ func (f *Fallback) MarshalJSON() ([]byte, error) {
|
||||
})
|
||||
}
|
||||
|
||||
// Unwrap implements C.ProxyAdapter
|
||||
func (f *Fallback) Unwrap(metadata *C.Metadata) C.Proxy {
|
||||
proxy := f.findAliveProxy(true)
|
||||
return proxy
|
@ -7,8 +7,8 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/adapters/outbound"
|
||||
"github.com/Dreamacro/clash/adapters/provider"
|
||||
"github.com/Dreamacro/clash/adapter/outbound"
|
||||
"github.com/Dreamacro/clash/adapter/provider"
|
||||
"github.com/Dreamacro/clash/common/murmur3"
|
||||
"github.com/Dreamacro/clash/common/singledo"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
@ -68,6 +68,7 @@ func jumpHash(key uint64, buckets int32) int32 {
|
||||
return int32(b)
|
||||
}
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) {
|
||||
defer func() {
|
||||
if err == nil {
|
||||
@ -81,6 +82,7 @@ func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata) (c
|
||||
return
|
||||
}
|
||||
|
||||
// DialUDP implements C.ProxyAdapter
|
||||
func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, err error) {
|
||||
defer func() {
|
||||
if err == nil {
|
||||
@ -93,6 +95,7 @@ func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, err error
|
||||
return proxy.DialUDP(metadata)
|
||||
}
|
||||
|
||||
// SupportUDP implements C.ProxyAdapter
|
||||
func (lb *LoadBalance) SupportUDP() bool {
|
||||
return !lb.disableUDP
|
||||
}
|
||||
@ -130,6 +133,7 @@ func strategyConsistentHashing() strategyFn {
|
||||
}
|
||||
}
|
||||
|
||||
// Unwrap implements C.ProxyAdapter
|
||||
func (lb *LoadBalance) Unwrap(metadata *C.Metadata) C.Proxy {
|
||||
proxies := lb.proxies(true)
|
||||
return lb.strategyFn(proxies, metadata)
|
||||
@ -143,6 +147,7 @@ func (lb *LoadBalance) proxies(touch bool) []C.Proxy {
|
||||
return elm.([]C.Proxy)
|
||||
}
|
||||
|
||||
// MarshalJSON implements C.ProxyAdapter
|
||||
func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
|
||||
var all []string
|
||||
for _, proxy := range lb.proxies(false) {
|
@ -4,7 +4,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/Dreamacro/clash/adapters/provider"
|
||||
"github.com/Dreamacro/clash/adapter/provider"
|
||||
"github.com/Dreamacro/clash/common/structure"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
@ -6,8 +6,8 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/Dreamacro/clash/adapters/outbound"
|
||||
"github.com/Dreamacro/clash/adapters/provider"
|
||||
"github.com/Dreamacro/clash/adapter/outbound"
|
||||
"github.com/Dreamacro/clash/adapter/provider"
|
||||
"github.com/Dreamacro/clash/common/singledo"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
@ -19,6 +19,7 @@ type Relay struct {
|
||||
providers []provider.ProxyProvider
|
||||
}
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||
proxies := r.proxies(metadata, true)
|
||||
if len(proxies) == 0 {
|
||||
@ -56,6 +57,7 @@ func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
|
||||
return outbound.NewConn(c, r), nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements C.ProxyAdapter
|
||||
func (r *Relay) MarshalJSON() ([]byte, error) {
|
||||
var all []string
|
||||
for _, proxy := range r.rawProxies(false) {
|
@ -5,8 +5,8 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"github.com/Dreamacro/clash/adapters/outbound"
|
||||
"github.com/Dreamacro/clash/adapters/provider"
|
||||
"github.com/Dreamacro/clash/adapter/outbound"
|
||||
"github.com/Dreamacro/clash/adapter/provider"
|
||||
"github.com/Dreamacro/clash/common/singledo"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
@ -19,6 +19,7 @@ type Selector struct {
|
||||
providers []provider.ProxyProvider
|
||||
}
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||
c, err := s.selectedProxy(true).DialContext(ctx, metadata)
|
||||
if err == nil {
|
||||
@ -27,6 +28,7 @@ func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Con
|
||||
return c, err
|
||||
}
|
||||
|
||||
// DialUDP implements C.ProxyAdapter
|
||||
func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
pc, err := s.selectedProxy(true).DialUDP(metadata)
|
||||
if err == nil {
|
||||
@ -35,6 +37,7 @@ func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
return pc, err
|
||||
}
|
||||
|
||||
// SupportUDP implements C.ProxyAdapter
|
||||
func (s *Selector) SupportUDP() bool {
|
||||
if s.disableUDP {
|
||||
return false
|
||||
@ -43,6 +46,7 @@ func (s *Selector) SupportUDP() bool {
|
||||
return s.selectedProxy(false).SupportUDP()
|
||||
}
|
||||
|
||||
// MarshalJSON implements C.ProxyAdapter
|
||||
func (s *Selector) MarshalJSON() ([]byte, error) {
|
||||
var all []string
|
||||
for _, proxy := range getProvidersProxies(s.providers, false) {
|
||||
@ -72,6 +76,7 @@ func (s *Selector) Set(name string) error {
|
||||
return errors.New("proxy not exist")
|
||||
}
|
||||
|
||||
// Unwrap implements C.ProxyAdapter
|
||||
func (s *Selector) Unwrap(metadata *C.Metadata) C.Proxy {
|
||||
return s.selectedProxy(true)
|
||||
}
|
@ -5,8 +5,8 @@ import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/adapters/outbound"
|
||||
"github.com/Dreamacro/clash/adapters/provider"
|
||||
"github.com/Dreamacro/clash/adapter/outbound"
|
||||
"github.com/Dreamacro/clash/adapter/provider"
|
||||
"github.com/Dreamacro/clash/common/singledo"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
@ -33,6 +33,7 @@ func (u *URLTest) Now() string {
|
||||
return u.fast(false).Name()
|
||||
}
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) {
|
||||
c, err = u.fast(true).DialContext(ctx, metadata)
|
||||
if err == nil {
|
||||
@ -41,6 +42,7 @@ func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Co
|
||||
return c, err
|
||||
}
|
||||
|
||||
// DialUDP implements C.ProxyAdapter
|
||||
func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
pc, err := u.fast(true).DialUDP(metadata)
|
||||
if err == nil {
|
||||
@ -49,6 +51,7 @@ func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
return pc, err
|
||||
}
|
||||
|
||||
// Unwrap implements C.ProxyAdapter
|
||||
func (u *URLTest) Unwrap(metadata *C.Metadata) C.Proxy {
|
||||
return u.fast(true)
|
||||
}
|
||||
@ -66,7 +69,13 @@ func (u *URLTest) fast(touch bool) C.Proxy {
|
||||
proxies := u.proxies(touch)
|
||||
fast := proxies[0]
|
||||
min := fast.LastDelay()
|
||||
fastNotExist := true
|
||||
|
||||
for _, proxy := range proxies[1:] {
|
||||
if u.fastNode != nil && proxy.Name() == u.fastNode.Name() {
|
||||
fastNotExist = false
|
||||
}
|
||||
|
||||
if !proxy.Alive() {
|
||||
continue
|
||||
}
|
||||
@ -79,7 +88,7 @@ func (u *URLTest) fast(touch bool) C.Proxy {
|
||||
}
|
||||
|
||||
// tolerance
|
||||
if u.fastNode == nil || u.fastNode.LastDelay() > fast.LastDelay()+u.tolerance {
|
||||
if u.fastNode == nil || fastNotExist || !u.fastNode.Alive() || u.fastNode.LastDelay() > fast.LastDelay()+u.tolerance {
|
||||
u.fastNode = fast
|
||||
}
|
||||
|
||||
@ -89,6 +98,7 @@ func (u *URLTest) fast(touch bool) C.Proxy {
|
||||
return elm.(C.Proxy)
|
||||
}
|
||||
|
||||
// SupportUDP implements C.ProxyAdapter
|
||||
func (u *URLTest) SupportUDP() bool {
|
||||
if u.disableUDP {
|
||||
return false
|
||||
@ -97,6 +107,7 @@ func (u *URLTest) SupportUDP() bool {
|
||||
return u.fast(false).SupportUDP()
|
||||
}
|
||||
|
||||
// MarshalJSON implements C.ProxyAdapter
|
||||
func (u *URLTest) MarshalJSON() ([]byte, error) {
|
||||
var all []string
|
||||
for _, proxy := range u.proxies(false) {
|
@ -16,25 +16,7 @@ func addrToMetadata(rawAddress string) (addr *C.Metadata, err error) {
|
||||
}
|
||||
|
||||
ip := net.ParseIP(host)
|
||||
if ip != nil {
|
||||
if ip.To4() != nil {
|
||||
addr = &C.Metadata{
|
||||
AddrType: C.AtypIPv4,
|
||||
Host: "",
|
||||
DstIP: ip,
|
||||
DstPort: port,
|
||||
}
|
||||
return
|
||||
} else {
|
||||
addr = &C.Metadata{
|
||||
AddrType: C.AtypIPv6,
|
||||
Host: "",
|
||||
DstIP: ip,
|
||||
DstPort: port,
|
||||
}
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if ip == nil {
|
||||
addr = &C.Metadata{
|
||||
AddrType: C.AtypDomainName,
|
||||
Host: host,
|
||||
@ -42,7 +24,23 @@ func addrToMetadata(rawAddress string) (addr *C.Metadata, err error) {
|
||||
DstPort: port,
|
||||
}
|
||||
return
|
||||
} else if ip4 := ip.To4(); ip4 != nil {
|
||||
addr = &C.Metadata{
|
||||
AddrType: C.AtypIPv4,
|
||||
Host: "",
|
||||
DstIP: ip4,
|
||||
DstPort: port,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
addr = &C.Metadata{
|
||||
AddrType: C.AtypIPv6,
|
||||
Host: "",
|
||||
DstIP: ip,
|
||||
DstPort: port,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func tcpKeepAlive(c net.Conn) {
|
@ -1,8 +1,9 @@
|
||||
package outbound
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/outbound"
|
||||
"github.com/Dreamacro/clash/common/structure"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
@ -20,36 +21,36 @@ func ParseProxy(mapping map[string]interface{}) (C.Proxy, error) {
|
||||
)
|
||||
switch proxyType {
|
||||
case "ss":
|
||||
ssOption := &ShadowSocksOption{}
|
||||
ssOption := &outbound.ShadowSocksOption{}
|
||||
err = decoder.Decode(mapping, ssOption)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
proxy, err = NewShadowSocks(*ssOption)
|
||||
proxy, err = outbound.NewShadowSocks(*ssOption)
|
||||
case "ssr":
|
||||
ssrOption := &ShadowSocksROption{}
|
||||
ssrOption := &outbound.ShadowSocksROption{}
|
||||
err = decoder.Decode(mapping, ssrOption)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
proxy, err = NewShadowSocksR(*ssrOption)
|
||||
proxy, err = outbound.NewShadowSocksR(*ssrOption)
|
||||
case "socks5":
|
||||
socksOption := &Socks5Option{}
|
||||
socksOption := &outbound.Socks5Option{}
|
||||
err = decoder.Decode(mapping, socksOption)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
proxy = NewSocks5(*socksOption)
|
||||
proxy = outbound.NewSocks5(*socksOption)
|
||||
case "http":
|
||||
httpOption := &HttpOption{}
|
||||
httpOption := &outbound.HttpOption{}
|
||||
err = decoder.Decode(mapping, httpOption)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
proxy = NewHttp(*httpOption)
|
||||
proxy = outbound.NewHttp(*httpOption)
|
||||
case "vmess":
|
||||
vmessOption := &VmessOption{
|
||||
HTTPOpts: HTTPOptions{
|
||||
vmessOption := &outbound.VmessOption{
|
||||
HTTPOpts: outbound.HTTPOptions{
|
||||
Method: "GET",
|
||||
Path: []string{"/"},
|
||||
},
|
||||
@ -58,21 +59,21 @@ func ParseProxy(mapping map[string]interface{}) (C.Proxy, error) {
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
proxy, err = NewVmess(*vmessOption)
|
||||
proxy, err = outbound.NewVmess(*vmessOption)
|
||||
case "snell":
|
||||
snellOption := &SnellOption{}
|
||||
snellOption := &outbound.SnellOption{}
|
||||
err = decoder.Decode(mapping, snellOption)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
proxy, err = NewSnell(*snellOption)
|
||||
proxy, err = outbound.NewSnell(*snellOption)
|
||||
case "trojan":
|
||||
trojanOption := &TrojanOption{}
|
||||
trojanOption := &outbound.TrojanOption{}
|
||||
err = decoder.Decode(mapping, trojanOption)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
proxy, err = NewTrojan(*trojanOption)
|
||||
proxy, err = outbound.NewTrojan(*trojanOption)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupport proxy type: %s", proxyType)
|
||||
}
|
@ -72,9 +72,11 @@ func (f *fetcher) Initial() (interface{}, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
isLocal = false
|
||||
}
|
||||
|
||||
if f.vehicle.Type() != File {
|
||||
if f.vehicle.Type() != File && !isLocal {
|
||||
if err := safeWrite(f.vehicle.Path(), buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -108,8 +110,10 @@ func (f *fetcher) Update() (interface{}, bool, error) {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
if err := safeWrite(f.vehicle.Path(), buf); err != nil {
|
||||
return nil, false, err
|
||||
if f.vehicle.Type() != File {
|
||||
if err := safeWrite(f.vehicle.Path(), buf); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
|
||||
f.updatedAt = &now
|
@ -40,7 +40,7 @@ func ParseProxyProvider(name string, mapping map[string]interface{}) (ProxyProvi
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var hcInterval uint = 0
|
||||
var hcInterval uint
|
||||
if schema.HealthCheck.Enable {
|
||||
hcInterval = uint(schema.HealthCheck.Interval)
|
||||
}
|
@ -7,7 +7,7 @@ import (
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/adapters/outbound"
|
||||
"github.com/Dreamacro/clash/adapter"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
@ -133,7 +133,7 @@ func proxiesParse(buf []byte) (interface{}, error) {
|
||||
|
||||
proxies := []C.Proxy{}
|
||||
for idx, mapping := range schema.Proxies {
|
||||
proxy, err := outbound.ParseProxy(mapping)
|
||||
proxy, err := adapter.ParseProxy(mapping)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("proxy %d error: %w", idx, err)
|
||||
}
|
@ -107,6 +107,7 @@ func (h *HTTPVehicle) Read() ([]byte, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
buf, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
@ -1,60 +0,0 @@
|
||||
package inbound
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
|
||||
// HTTPAdapter is a adapter for HTTP connection
|
||||
type HTTPAdapter struct {
|
||||
net.Conn
|
||||
metadata *C.Metadata
|
||||
R *http.Request
|
||||
}
|
||||
|
||||
// Metadata return destination metadata
|
||||
func (h *HTTPAdapter) Metadata() *C.Metadata {
|
||||
return h.metadata
|
||||
}
|
||||
|
||||
// NewHTTP is HTTPAdapter generator
|
||||
func NewHTTP(request *http.Request, conn net.Conn) *HTTPAdapter {
|
||||
metadata := parseHTTPAddr(request)
|
||||
metadata.Type = C.HTTP
|
||||
if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil {
|
||||
metadata.SrcIP = ip
|
||||
metadata.SrcPort = port
|
||||
}
|
||||
return &HTTPAdapter{
|
||||
metadata: metadata,
|
||||
R: request,
|
||||
Conn: conn,
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveHopByHopHeaders remove hop-by-hop header
|
||||
func RemoveHopByHopHeaders(header http.Header) {
|
||||
// Strip hop-by-hop header based on RFC:
|
||||
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1
|
||||
// https://www.mnot.net/blog/2011/07/11/what_proxies_must_do
|
||||
|
||||
header.Del("Proxy-Connection")
|
||||
header.Del("Proxy-Authenticate")
|
||||
header.Del("Proxy-Authorization")
|
||||
header.Del("TE")
|
||||
header.Del("Trailers")
|
||||
header.Del("Transfer-Encoding")
|
||||
header.Del("Upgrade")
|
||||
|
||||
connections := header.Get("Connection")
|
||||
header.Del("Connection")
|
||||
if len(connections) == 0 {
|
||||
return
|
||||
}
|
||||
for _, h := range strings.Split(connections, ",") {
|
||||
header.Del(strings.TrimSpace(h))
|
||||
}
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
package inbound
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/component/socks5"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
|
||||
// SocketAdapter is a adapter for socks and redir connection
|
||||
type SocketAdapter struct {
|
||||
net.Conn
|
||||
metadata *C.Metadata
|
||||
}
|
||||
|
||||
// Metadata return destination metadata
|
||||
func (s *SocketAdapter) Metadata() *C.Metadata {
|
||||
return s.metadata
|
||||
}
|
||||
|
||||
// NewSocket is SocketAdapter generator
|
||||
func NewSocket(target socks5.Addr, conn net.Conn, source C.Type) *SocketAdapter {
|
||||
metadata := parseSocksAddr(target)
|
||||
metadata.NetWork = C.TCP
|
||||
metadata.Type = source
|
||||
if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil {
|
||||
metadata.SrcIP = ip
|
||||
metadata.SrcPort = port
|
||||
}
|
||||
|
||||
return &SocketAdapter{
|
||||
Conn: conn,
|
||||
metadata: metadata,
|
||||
}
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/trojan"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
|
||||
type Trojan struct {
|
||||
*Base
|
||||
instance *trojan.Trojan
|
||||
}
|
||||
|
||||
type TrojanOption struct {
|
||||
Name string `proxy:"name"`
|
||||
Server string `proxy:"server"`
|
||||
Port int `proxy:"port"`
|
||||
Password string `proxy:"password"`
|
||||
ALPN []string `proxy:"alpn,omitempty"`
|
||||
SNI string `proxy:"sni,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
UDP bool `proxy:"udp,omitempty"`
|
||||
}
|
||||
|
||||
func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
c, err := t.instance.StreamConn(c)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||
}
|
||||
|
||||
err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata))
|
||||
return c, err
|
||||
}
|
||||
|
||||
func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||
c, err := dialer.DialContext(ctx, "tcp", t.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
c, err = t.StreamConn(c, metadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewConn(c, t), err
|
||||
}
|
||||
|
||||
func (t *Trojan) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
|
||||
defer cancel()
|
||||
c, err := dialer.DialContext(ctx, "tcp", t.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
c, err = t.instance.StreamConn(c)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||
}
|
||||
|
||||
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pc := t.instance.PacketConn(c)
|
||||
return newPacketConn(pc, t), err
|
||||
}
|
||||
|
||||
func (t *Trojan) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(map[string]string{
|
||||
"type": t.Type().String(),
|
||||
})
|
||||
}
|
||||
|
||||
func NewTrojan(option TrojanOption) (*Trojan, error) {
|
||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||
|
||||
tOption := &trojan.Option{
|
||||
Password: option.Password,
|
||||
ALPN: option.ALPN,
|
||||
ServerName: option.Server,
|
||||
SkipCertVerify: option.SkipCertVerify,
|
||||
ClientSessionCache: getClientSessionCache(),
|
||||
}
|
||||
|
||||
if option.SNI != "" {
|
||||
tOption.ServerName = option.SNI
|
||||
}
|
||||
|
||||
return &Trojan{
|
||||
Base: &Base{
|
||||
name: option.Name,
|
||||
addr: addr,
|
||||
tp: C.Trojan,
|
||||
udp: option.UDP,
|
||||
},
|
||||
instance: trojan.New(tOption),
|
||||
}, nil
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package mixed
|
||||
package net
|
||||
|
||||
import (
|
||||
"bufio"
|
11
common/net/io.go
Normal file
11
common/net/io.go
Normal file
@ -0,0 +1,11 @@
|
||||
package net
|
||||
|
||||
import "io"
|
||||
|
||||
type ReadOnlyReader struct {
|
||||
io.Reader
|
||||
}
|
||||
|
||||
type WriteOnlyWriter struct {
|
||||
io.Writer
|
||||
}
|
@ -3,8 +3,14 @@ package dialer
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/singledo"
|
||||
)
|
||||
|
||||
// In some OS, such as Windows, it takes a little longer to get interface information
|
||||
var ifaceSingle = singledo.NewSingle(time.Second * 20)
|
||||
|
||||
var (
|
||||
errPlatformNotSupport = errors.New("unsupport platform")
|
||||
)
|
||||
@ -52,12 +58,18 @@ func lookupUDPAddr(ip net.IP, addrs []net.Addr) (*net.UDPAddr, error) {
|
||||
}
|
||||
|
||||
func fallbackBindToDialer(dialer *net.Dialer, network string, ip net.IP, name string) error {
|
||||
iface, err := net.InterfaceByName(name)
|
||||
if !ip.IsGlobalUnicast() {
|
||||
return nil
|
||||
}
|
||||
|
||||
iface, err, _ := ifaceSingle.Do(func() (interface{}, error) {
|
||||
return net.InterfaceByName(name)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
addrs, err := iface.Addrs()
|
||||
addrs, err := iface.(*net.Interface).Addrs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -81,12 +93,14 @@ func fallbackBindToDialer(dialer *net.Dialer, network string, ip net.IP, name st
|
||||
}
|
||||
|
||||
func fallbackBindToListenConfig(name string) (string, error) {
|
||||
iface, err := net.InterfaceByName(name)
|
||||
iface, err, _ := ifaceSingle.Do(func() (interface{}, error) {
|
||||
return net.InterfaceByName(name)
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
addrs, err := iface.Addrs()
|
||||
addrs, err := iface.(*net.Interface).Addrs()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -29,23 +29,25 @@ func bindControl(ifaceIdx int) controlFn {
|
||||
}
|
||||
|
||||
func bindIfaceToDialer(dialer *net.Dialer, ifaceName string) error {
|
||||
iface, err := net.InterfaceByName(ifaceName)
|
||||
iface, err, _ := ifaceSingle.Do(func() (interface{}, error) {
|
||||
return net.InterfaceByName(ifaceName)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dialer.Control = bindControl(iface.Index)
|
||||
|
||||
dialer.Control = bindControl(iface.(*net.Interface).Index)
|
||||
return nil
|
||||
}
|
||||
|
||||
func bindIfaceToListenConfig(lc *net.ListenConfig, ifaceName string) error {
|
||||
iface, err := net.InterfaceByName(ifaceName)
|
||||
iface, err, _ := ifaceSingle.Do(func() (interface{}, error) {
|
||||
return net.InterfaceByName(ifaceName)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lc.Control = bindControl(iface.Index)
|
||||
|
||||
lc.Control = bindControl(iface.(*net.Interface).Index)
|
||||
return nil
|
||||
}
|
||||
|
@ -1,12 +1,13 @@
|
||||
package process
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"net"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -94,12 +95,8 @@ func getExecPathFromPID(pid uint32) (string, error) {
|
||||
if errno != 0 {
|
||||
return "", errno
|
||||
}
|
||||
firstZero := bytes.IndexByte(buf, 0)
|
||||
if firstZero <= 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return filepath.Base(string(buf[:firstZero])), nil
|
||||
return filepath.Base(unix.ByteSliceToString(buf)), nil
|
||||
}
|
||||
|
||||
func readNativeUint32(b []byte) uint32 {
|
||||
|
@ -30,6 +30,10 @@ func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
|
||||
}
|
||||
})
|
||||
|
||||
if defaultSearcher == nil {
|
||||
return "", ErrPlatformNotSupport
|
||||
}
|
||||
|
||||
var spath string
|
||||
isTCP := network == TCP
|
||||
switch network {
|
||||
@ -173,7 +177,7 @@ func (s *searcher) searchSocketPid(socket uint64) (uint32, error) {
|
||||
}
|
||||
|
||||
func newSearcher(major int) *searcher {
|
||||
var s *searcher = nil
|
||||
var s *searcher
|
||||
switch major {
|
||||
case 11:
|
||||
s = &searcher{
|
||||
@ -190,6 +194,8 @@ func newSearcher(major int) *searcher {
|
||||
udpInpOffset: 8,
|
||||
}
|
||||
case 12:
|
||||
fallthrough
|
||||
case 13:
|
||||
s = &searcher{
|
||||
headSize: 64,
|
||||
tcpItemSize: 744,
|
||||
|
@ -22,8 +22,8 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
getExTcpTable uintptr
|
||||
getExUdpTable uintptr
|
||||
getExTCPTable uintptr
|
||||
getExUDPTable uintptr
|
||||
queryProcName uintptr
|
||||
|
||||
once sync.Once
|
||||
@ -35,12 +35,12 @@ func initWin32API() error {
|
||||
return fmt.Errorf("LoadLibrary iphlpapi.dll failed: %s", err.Error())
|
||||
}
|
||||
|
||||
getExTcpTable, err = windows.GetProcAddress(h, tcpTableFunc)
|
||||
getExTCPTable, err = windows.GetProcAddress(h, tcpTableFunc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetProcAddress of %s failed: %s", tcpTableFunc, err.Error())
|
||||
}
|
||||
|
||||
getExUdpTable, err = windows.GetProcAddress(h, udpTableFunc)
|
||||
getExUDPTable, err = windows.GetProcAddress(h, udpTableFunc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetProcAddress of %s failed: %s", udpTableFunc, err.Error())
|
||||
}
|
||||
@ -76,10 +76,10 @@ func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
|
||||
var fn uintptr
|
||||
switch network {
|
||||
case TCP:
|
||||
fn = getExTcpTable
|
||||
fn = getExTCPTable
|
||||
class = tcpTablePidConn
|
||||
case UDP:
|
||||
fn = getExUdpTable
|
||||
fn = getExUDPTable
|
||||
class = udpTablePid
|
||||
default:
|
||||
return "", ErrInvalidNetwork
|
||||
|
101
component/profile/cachefile/cache.go
Normal file
101
component/profile/cachefile/cache.go
Normal file
@ -0,0 +1,101 @@
|
||||
package cachefile
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/Dreamacro/clash/component/profile"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
)
|
||||
|
||||
var (
|
||||
initOnce sync.Once
|
||||
fileMode os.FileMode = 0666
|
||||
defaultCache *CacheFile
|
||||
)
|
||||
|
||||
type cache struct {
|
||||
Selected map[string]string
|
||||
}
|
||||
|
||||
// CacheFile store and update the cache file
|
||||
type CacheFile struct {
|
||||
path string
|
||||
model *cache
|
||||
buf *bytes.Buffer
|
||||
mux sync.Mutex
|
||||
}
|
||||
|
||||
func (c *CacheFile) SetSelected(group, selected string) {
|
||||
if !profile.StoreSelected.Load() {
|
||||
return
|
||||
}
|
||||
|
||||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
|
||||
model := c.element()
|
||||
|
||||
model.Selected[group] = selected
|
||||
c.buf.Reset()
|
||||
if err := gob.NewEncoder(c.buf).Encode(model); err != nil {
|
||||
log.Warnln("[CacheFile] encode gob failed: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(c.path, c.buf.Bytes(), fileMode); err != nil {
|
||||
log.Warnln("[CacheFile] write cache to %s failed: %s", c.path, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CacheFile) SelectedMap() map[string]string {
|
||||
if !profile.StoreSelected.Load() {
|
||||
return nil
|
||||
}
|
||||
|
||||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
|
||||
model := c.element()
|
||||
|
||||
mapping := map[string]string{}
|
||||
for k, v := range model.Selected {
|
||||
mapping[k] = v
|
||||
}
|
||||
return mapping
|
||||
}
|
||||
|
||||
func (c *CacheFile) element() *cache {
|
||||
if c.model != nil {
|
||||
return c.model
|
||||
}
|
||||
|
||||
model := &cache{
|
||||
Selected: map[string]string{},
|
||||
}
|
||||
|
||||
if buf, err := ioutil.ReadFile(c.path); err == nil {
|
||||
bufReader := bytes.NewBuffer(buf)
|
||||
gob.NewDecoder(bufReader).Decode(model)
|
||||
}
|
||||
|
||||
c.model = model
|
||||
return c.model
|
||||
}
|
||||
|
||||
// Cache return singleton of CacheFile
|
||||
func Cache() *CacheFile {
|
||||
initOnce.Do(func() {
|
||||
defaultCache = &CacheFile{
|
||||
path: C.Path.Cache(),
|
||||
buf: &bytes.Buffer{},
|
||||
}
|
||||
})
|
||||
|
||||
return defaultCache
|
||||
}
|
10
component/profile/profile.go
Normal file
10
component/profile/profile.go
Normal file
@ -0,0 +1,10 @@
|
||||
package profile
|
||||
|
||||
import (
|
||||
"go.uber.org/atomic"
|
||||
)
|
||||
|
||||
var (
|
||||
// StoreSelected is a global switch for storing selected proxy to cache
|
||||
StoreSelected = atomic.NewBool(true)
|
||||
)
|
@ -1,9 +1,12 @@
|
||||
package resolver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"math/rand"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/component/trie"
|
||||
)
|
||||
@ -18,6 +21,9 @@ var (
|
||||
|
||||
// DefaultHosts aim to resolve hosts
|
||||
DefaultHosts = trie.New()
|
||||
|
||||
// DefaultDNSTimeout defined the default dns request timeout
|
||||
DefaultDNSTimeout = time.Second * 5
|
||||
)
|
||||
|
||||
var (
|
||||
@ -52,18 +58,16 @@ func ResolveIPv4(host string) (net.IP, error) {
|
||||
return DefaultResolver.ResolveIPv4(host)
|
||||
}
|
||||
|
||||
ipAddrs, err := net.LookupIP(host)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), DefaultDNSTimeout)
|
||||
defer cancel()
|
||||
ipAddrs, err := net.DefaultResolver.LookupIP(ctx, "ip4", host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if len(ipAddrs) == 0 {
|
||||
return nil, ErrIPNotFound
|
||||
}
|
||||
|
||||
for _, ip := range ipAddrs {
|
||||
if ip4 := ip.To4(); ip4 != nil {
|
||||
return ip4, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, ErrIPNotFound
|
||||
return ipAddrs[rand.Intn(len(ipAddrs))], nil
|
||||
}
|
||||
|
||||
// ResolveIPv6 with a host, return ipv6
|
||||
@ -90,31 +94,29 @@ func ResolveIPv6(host string) (net.IP, error) {
|
||||
return DefaultResolver.ResolveIPv6(host)
|
||||
}
|
||||
|
||||
ipAddrs, err := net.LookupIP(host)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), DefaultDNSTimeout)
|
||||
defer cancel()
|
||||
ipAddrs, err := net.DefaultResolver.LookupIP(ctx, "ip6", host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if len(ipAddrs) == 0 {
|
||||
return nil, ErrIPNotFound
|
||||
}
|
||||
|
||||
for _, ip := range ipAddrs {
|
||||
if ip.To4() == nil {
|
||||
return ip, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, ErrIPNotFound
|
||||
return ipAddrs[rand.Intn(len(ipAddrs))], nil
|
||||
}
|
||||
|
||||
// ResolveIP with a host, return ip
|
||||
func ResolveIP(host string) (net.IP, error) {
|
||||
// ResolveIPWithResolver same as ResolveIP, but with a resolver
|
||||
func ResolveIPWithResolver(host string, r Resolver) (net.IP, error) {
|
||||
if node := DefaultHosts.Search(host); node != nil {
|
||||
return node.Data.(net.IP), nil
|
||||
}
|
||||
|
||||
if DefaultResolver != nil {
|
||||
if r != nil {
|
||||
if DisableIPv6 {
|
||||
return DefaultResolver.ResolveIPv4(host)
|
||||
return r.ResolveIPv4(host)
|
||||
}
|
||||
return DefaultResolver.ResolveIP(host)
|
||||
return r.ResolveIP(host)
|
||||
} else if DisableIPv6 {
|
||||
return ResolveIPv4(host)
|
||||
}
|
||||
@ -131,3 +133,8 @@ func ResolveIP(host string) (net.IP, error) {
|
||||
|
||||
return ipAddr.IP, nil
|
||||
}
|
||||
|
||||
// ResolveIP with a host, return ip
|
||||
func ResolveIP(host string) (net.IP, error) {
|
||||
return ResolveIPWithResolver(host, DefaultResolver)
|
||||
}
|
||||
|
@ -1,11 +0,0 @@
|
||||
package obfs
|
||||
|
||||
// Base information for obfs
|
||||
type Base struct {
|
||||
IVSize int
|
||||
Key []byte
|
||||
HeadLen int
|
||||
Host string
|
||||
Port int
|
||||
Param string
|
||||
}
|
@ -1,402 +0,0 @@
|
||||
package obfs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type httpObfs struct {
|
||||
*Base
|
||||
firstRequest bool
|
||||
firstResponse bool
|
||||
post bool
|
||||
}
|
||||
|
||||
func init() {
|
||||
register("http_simple", newHTTPSimple)
|
||||
}
|
||||
|
||||
func newHTTPSimple(b *Base) Obfs {
|
||||
return &httpObfs{Base: b}
|
||||
}
|
||||
|
||||
func (h *httpObfs) initForConn() Obfs {
|
||||
return &httpObfs{
|
||||
Base: h.Base,
|
||||
firstRequest: true,
|
||||
firstResponse: true,
|
||||
post: h.post,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *httpObfs) GetObfsOverhead() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (h *httpObfs) Decode(b []byte) ([]byte, bool, error) {
|
||||
if h.firstResponse {
|
||||
idx := bytes.Index(b, []byte("\r\n\r\n"))
|
||||
if idx == -1 {
|
||||
return nil, false, io.EOF
|
||||
}
|
||||
h.firstResponse = false
|
||||
return b[idx+4:], false, nil
|
||||
}
|
||||
return b, false, nil
|
||||
}
|
||||
|
||||
func (h *httpObfs) Encode(b []byte) ([]byte, error) {
|
||||
if h.firstRequest {
|
||||
bSize := len(b)
|
||||
var headData []byte
|
||||
|
||||
if headSize := h.IVSize + h.HeadLen; bSize-headSize > 64 {
|
||||
headData = make([]byte, headSize+rand.Intn(64))
|
||||
} else {
|
||||
headData = make([]byte, bSize)
|
||||
}
|
||||
copy(headData, b[:len(headData)])
|
||||
host := h.Host
|
||||
var customHead string
|
||||
|
||||
if len(h.Param) > 0 {
|
||||
customHeads := strings.Split(h.Param, "#")
|
||||
if len(customHeads) > 2 {
|
||||
customHeads = customHeads[:2]
|
||||
}
|
||||
customHosts := h.Param
|
||||
if len(customHeads) > 1 {
|
||||
customHosts = customHeads[0]
|
||||
customHead = customHeads[1]
|
||||
}
|
||||
hosts := strings.Split(customHosts, ",")
|
||||
if len(hosts) > 0 {
|
||||
host = strings.TrimSpace(hosts[rand.Intn(len(hosts))])
|
||||
}
|
||||
}
|
||||
|
||||
method := "GET /"
|
||||
if h.post {
|
||||
method = "POST /"
|
||||
}
|
||||
requestPathIndex := rand.Intn(len(requestPath)/2) * 2
|
||||
httpBuf := fmt.Sprintf("%s%s%s%s HTTP/1.1\r\nHost: %s:%d\r\n",
|
||||
method,
|
||||
requestPath[requestPathIndex],
|
||||
data2URLEncode(headData),
|
||||
requestPath[requestPathIndex+1],
|
||||
host, h.Port)
|
||||
if len(customHead) > 0 {
|
||||
httpBuf = httpBuf + strings.Replace(customHead, "\\n", "\r\n", -1) + "\r\n\r\n"
|
||||
} else {
|
||||
var contentType string
|
||||
if h.post {
|
||||
contentType = "Content-Type: multipart/form-data; boundary=" + boundary() + "\r\n"
|
||||
}
|
||||
httpBuf = httpBuf + "User-agent: " + requestUserAgent[rand.Intn(len(requestUserAgent))] + "\r\n" +
|
||||
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" +
|
||||
"Accept-Language: en-US,en;q=0.8\r\n" +
|
||||
"Accept-Encoding: gzip, deflate\r\n" +
|
||||
contentType +
|
||||
"DNT: 1\r\n" +
|
||||
"Connection: keep-alive\r\n" +
|
||||
"\r\n"
|
||||
}
|
||||
|
||||
var encoded []byte
|
||||
if len(headData) < bSize {
|
||||
encoded = make([]byte, len(httpBuf)+(bSize-len(headData)))
|
||||
copy(encoded, []byte(httpBuf))
|
||||
copy(encoded[len(httpBuf):], b[len(headData):])
|
||||
} else {
|
||||
encoded = []byte(httpBuf)
|
||||
}
|
||||
h.firstRequest = false
|
||||
return encoded, nil
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func data2URLEncode(data []byte) (ret string) {
|
||||
for i := 0; i < len(data); i++ {
|
||||
ret = fmt.Sprintf("%s%%%s", ret, hex.EncodeToString([]byte{data[i]}))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func boundary() (ret string) {
|
||||
set := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||
for i := 0; i < 32; i++ {
|
||||
ret = fmt.Sprintf("%s%c", ret, set[rand.Intn(len(set))])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
requestPath = []string{
|
||||
"", "",
|
||||
"login.php?redir=", "",
|
||||
"register.php?code=", "",
|
||||
"?keyword=", "",
|
||||
"search?src=typd&q=", "&lang=en",
|
||||
"s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&ch=&bar=&wd=", "&rn=",
|
||||
"post.php?id=", "&goto=view.php",
|
||||
}
|
||||
requestUserAgent = []string{
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; Moto C Build/NRD90M.059) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/55.0.2883.91 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.111 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 5.1.1; SM-J120M Build/LMY47X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; Moto G (5) Build/NPPS25.137-93-14) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; SM-G570M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 5.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0; CAM-L03 Build/HUAWEICAM-L03) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.76 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.517.44 Safari/534.7",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3",
|
||||
"Mozilla/5.0 (Linux; Android 8.0.0; FIG-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.115 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.237 Safari/534.10",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.1 Safari/533.2",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Datanyze; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 5.1.1; SM-J111M Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.107 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0.1; SM-J700M Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.63 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Slackware/Chrome/12.0.742.100 Safari/534.30",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.167 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.116 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.100 Safari/534.30",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 8.0.0; WAS-LX3 Build/HUAWEIWAS-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.1805 Safari/537.36 MVisionPlayer/1.0.0.0",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; TRT-LX3 Build/HUAWEITRT-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.89 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0; vivo 1610 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 4.4.2; de-de; SAMSUNG GT-I9195 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Version/1.5 Chrome/28.0.1500.94 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 8.0.0; ANE-LX3 Build/HUAWEIANE-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; U; Linux i586; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.1 Safari/533.2",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.65 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; SM-G610M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0.1; SM-J500M Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.517.44 Safari/534.7",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.104 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0; vivo 1606 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; SM-G610M Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.1; vivo 1716 Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.98 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.93 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; SM-G570M Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0; MYA-L22 Build/HUAWEIMYA-L22) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 5.1; A1601 Build/LMY47I) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.98 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; TRT-LX2 Build/HUAWEITRT-LX2; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/59.0.3071.125 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/10.0.649.0 Safari/534.17",
|
||||
"Mozilla/5.0 (Linux; Android 6.0; CAM-L21 Build/HUAWEICAM-L21; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/62.0.3202.84 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.3 Safari/534.24",
|
||||
"Mozilla/5.0 (Linux; Android 7.1.2; Redmi 4X Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 4.4.2; SM-G7102 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.109 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 5.1; HUAWEI CUN-L22 Build/HUAWEICUN-L22; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/62.0.3202.84 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 5.1.1; A37fw Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; SM-J730GM Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; SM-G610F Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.1.2; Redmi Note 5A Build/N2G47H; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/63.0.3239.111 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; Redmi Note 4 Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Unknown; Linux) AppleWebKit/538.1 (KHTML, like Gecko) Chrome/v1.0.0 Safari/538.1",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; BLL-L22 Build/HUAWEIBLL-L22) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; SM-J710F Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.1.1; CPH1723 Build/N6F26Q) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.98 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.94 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 8.0.0; FIG-LX3 Build/HUAWEIFIG-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows; U; Windows NT 6.1; de-DE) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/10.0.649.0 Safari/534.17",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.63 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.65 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.1; Mi A1 Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.99 Safari/533.4",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36 MVisionPlayer/1.0.0.0",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 5.1; A37f Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.93 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.76 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0.1; CPH1607 Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/63.0.3239.111 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0.1; vivo 1603 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0.1; Redmi 4A Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/60.0.3112.116 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.71 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.64 Safari/537.31",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.143 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0.1; SM-G532G Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.83 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.109 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.131 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0; vivo 1713 Build/MRA58K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36",
|
||||
}
|
||||
)
|
@ -1,37 +0,0 @@
|
||||
package obfs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
errTLS12TicketAuthIncorrectMagicNumber = errors.New("tls1.2_ticket_auth incorrect magic number")
|
||||
errTLS12TicketAuthTooShortData = errors.New("tls1.2_ticket_auth too short data")
|
||||
errTLS12TicketAuthHMACError = errors.New("tls1.2_ticket_auth hmac verifying failed")
|
||||
)
|
||||
|
||||
// Obfs provides methods for decoding and encoding
|
||||
type Obfs interface {
|
||||
initForConn() Obfs
|
||||
GetObfsOverhead() int
|
||||
Decode(b []byte) ([]byte, bool, error)
|
||||
Encode(b []byte) ([]byte, error)
|
||||
}
|
||||
|
||||
type obfsCreator func(b *Base) Obfs
|
||||
|
||||
var obfsList = make(map[string]obfsCreator)
|
||||
|
||||
func register(name string, c obfsCreator) {
|
||||
obfsList[name] = c
|
||||
}
|
||||
|
||||
// PickObfs returns an obfs of the given name
|
||||
func PickObfs(name string, b *Base) (Obfs, error) {
|
||||
if obfsCreator, ok := obfsList[strings.ToLower(name)]; ok {
|
||||
return obfsCreator(b), nil
|
||||
}
|
||||
return nil, fmt.Errorf("Obfs %s not supported", name)
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
package obfs
|
||||
|
||||
type plain struct{}
|
||||
|
||||
func init() {
|
||||
register("plain", newPlain)
|
||||
}
|
||||
|
||||
func newPlain(b *Base) Obfs {
|
||||
return &plain{}
|
||||
}
|
||||
|
||||
func (p *plain) initForConn() Obfs { return &plain{} }
|
||||
|
||||
func (p *plain) GetObfsOverhead() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (p *plain) Encode(b []byte) ([]byte, error) {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (p *plain) Decode(b []byte) ([]byte, bool, error) {
|
||||
return b, false, nil
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
package obfs
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"hash/crc32"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
type randomHead struct {
|
||||
*Base
|
||||
firstRequest bool
|
||||
firstResponse bool
|
||||
headerSent bool
|
||||
buffer []byte
|
||||
}
|
||||
|
||||
func init() {
|
||||
register("random_head", newRandomHead)
|
||||
}
|
||||
|
||||
func newRandomHead(b *Base) Obfs {
|
||||
return &randomHead{Base: b}
|
||||
}
|
||||
|
||||
func (r *randomHead) initForConn() Obfs {
|
||||
return &randomHead{
|
||||
Base: r.Base,
|
||||
firstRequest: true,
|
||||
firstResponse: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *randomHead) GetObfsOverhead() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (r *randomHead) Encode(b []byte) (encoded []byte, err error) {
|
||||
if !r.firstRequest {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
bSize := len(b)
|
||||
if r.headerSent {
|
||||
if bSize > 0 {
|
||||
d := make([]byte, len(r.buffer)+bSize)
|
||||
copy(d, r.buffer)
|
||||
copy(d[len(r.buffer):], b)
|
||||
r.buffer = d
|
||||
} else {
|
||||
encoded = r.buffer
|
||||
r.buffer = nil
|
||||
r.firstRequest = false
|
||||
}
|
||||
} else {
|
||||
size := rand.Intn(96) + 8
|
||||
encoded = make([]byte, size)
|
||||
rand.Read(encoded)
|
||||
crc := (0xFFFFFFFF - crc32.ChecksumIEEE(encoded[:size-4])) & 0xFFFFFFFF
|
||||
binary.LittleEndian.PutUint32(encoded[size-4:], crc)
|
||||
|
||||
d := make([]byte, bSize)
|
||||
copy(d, b)
|
||||
r.buffer = d
|
||||
}
|
||||
r.headerSent = true
|
||||
return encoded, nil
|
||||
}
|
||||
|
||||
func (r *randomHead) Decode(b []byte) ([]byte, bool, error) {
|
||||
if r.firstResponse {
|
||||
r.firstResponse = false
|
||||
return b, true, nil
|
||||
}
|
||||
return b, false, nil
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
package obfs
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
)
|
||||
|
||||
// NewConn wraps a stream-oriented net.Conn with obfs decoding/encoding
|
||||
func NewConn(c net.Conn, o Obfs) net.Conn {
|
||||
return &Conn{Conn: c, Obfs: o.initForConn()}
|
||||
}
|
||||
|
||||
// Conn represents an obfs connection
|
||||
type Conn struct {
|
||||
net.Conn
|
||||
Obfs
|
||||
buf []byte
|
||||
offset int
|
||||
}
|
||||
|
||||
func (c *Conn) Read(b []byte) (int, error) {
|
||||
if c.buf != nil {
|
||||
n := copy(b, c.buf[c.offset:])
|
||||
c.offset += n
|
||||
if c.offset == len(c.buf) {
|
||||
pool.Put(c.buf)
|
||||
c.buf = nil
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
buf := pool.Get(pool.RelayBufferSize)
|
||||
defer pool.Put(buf)
|
||||
n, err := c.Conn.Read(buf)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
decoded, sendback, err := c.Decode(buf[:n])
|
||||
// decoded may be part of buf
|
||||
decodedData := pool.Get(len(decoded))
|
||||
copy(decodedData, decoded)
|
||||
if err != nil {
|
||||
pool.Put(decodedData)
|
||||
return 0, err
|
||||
}
|
||||
if sendback {
|
||||
c.Write(nil)
|
||||
pool.Put(decodedData)
|
||||
return 0, nil
|
||||
}
|
||||
n = copy(b, decodedData)
|
||||
if len(decodedData) > len(b) {
|
||||
c.buf = decodedData
|
||||
c.offset = n
|
||||
} else {
|
||||
pool.Put(decodedData)
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (c *Conn) Write(b []byte) (int, error) {
|
||||
encoded, err := c.Encode(b)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
_, err = c.Conn.Write(encoded)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
@ -1,290 +0,0 @@
|
||||
package obfs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
"github.com/Dreamacro/clash/component/ssr/tools"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
)
|
||||
|
||||
type tlsAuthData struct {
|
||||
localClientID [32]byte
|
||||
}
|
||||
|
||||
type tls12Ticket struct {
|
||||
*Base
|
||||
*tlsAuthData
|
||||
handshakeStatus int
|
||||
sendSaver bytes.Buffer
|
||||
recvBuffer bytes.Buffer
|
||||
buffer bytes.Buffer
|
||||
}
|
||||
|
||||
func init() {
|
||||
register("tls1.2_ticket_auth", newTLS12Ticket)
|
||||
register("tls1.2_ticket_fastauth", newTLS12Ticket)
|
||||
}
|
||||
|
||||
func newTLS12Ticket(b *Base) Obfs {
|
||||
return &tls12Ticket{
|
||||
Base: b,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *tls12Ticket) initForConn() Obfs {
|
||||
r := &tls12Ticket{
|
||||
Base: t.Base,
|
||||
tlsAuthData: &tlsAuthData{},
|
||||
}
|
||||
rand.Read(r.localClientID[:])
|
||||
return r
|
||||
}
|
||||
|
||||
func (t *tls12Ticket) GetObfsOverhead() int {
|
||||
return 5
|
||||
}
|
||||
|
||||
func (t *tls12Ticket) Decode(b []byte) ([]byte, bool, error) {
|
||||
if t.handshakeStatus == -1 {
|
||||
return b, false, nil
|
||||
}
|
||||
t.buffer.Reset()
|
||||
if t.handshakeStatus == 8 {
|
||||
t.recvBuffer.Write(b)
|
||||
for t.recvBuffer.Len() > 5 {
|
||||
var h [5]byte
|
||||
t.recvBuffer.Read(h[:])
|
||||
if !bytes.Equal(h[:3], []byte{0x17, 0x3, 0x3}) {
|
||||
log.Warnln("incorrect magic number %x, 0x170303 is expected", h[:3])
|
||||
return nil, false, errTLS12TicketAuthIncorrectMagicNumber
|
||||
}
|
||||
size := int(binary.BigEndian.Uint16(h[3:5]))
|
||||
if t.recvBuffer.Len() < size {
|
||||
// 不够读,下回再读吧
|
||||
unread := t.recvBuffer.Bytes()
|
||||
t.recvBuffer.Reset()
|
||||
t.recvBuffer.Write(h[:])
|
||||
t.recvBuffer.Write(unread)
|
||||
break
|
||||
}
|
||||
d := pool.Get(size)
|
||||
t.recvBuffer.Read(d)
|
||||
t.buffer.Write(d)
|
||||
pool.Put(d)
|
||||
}
|
||||
return t.buffer.Bytes(), false, nil
|
||||
}
|
||||
|
||||
if len(b) < 11+32+1+32 {
|
||||
return nil, false, errTLS12TicketAuthTooShortData
|
||||
}
|
||||
|
||||
hash := t.hmacSHA1(b[11 : 11+22])
|
||||
|
||||
if !hmac.Equal(b[33:33+tools.HmacSHA1Len], hash) {
|
||||
return nil, false, errTLS12TicketAuthHMACError
|
||||
}
|
||||
return nil, true, nil
|
||||
}
|
||||
|
||||
func (t *tls12Ticket) Encode(b []byte) ([]byte, error) {
|
||||
t.buffer.Reset()
|
||||
switch t.handshakeStatus {
|
||||
case 8:
|
||||
if len(b) < 1024 {
|
||||
d := []byte{0x17, 0x3, 0x3, 0, 0}
|
||||
binary.BigEndian.PutUint16(d[3:5], uint16(len(b)&0xFFFF))
|
||||
t.buffer.Write(d)
|
||||
t.buffer.Write(b)
|
||||
return t.buffer.Bytes(), nil
|
||||
}
|
||||
start := 0
|
||||
var l int
|
||||
for len(b)-start > 2048 {
|
||||
l = rand.Intn(4096) + 100
|
||||
if l > len(b)-start {
|
||||
l = len(b) - start
|
||||
}
|
||||
packData(&t.buffer, b[start:start+l])
|
||||
start += l
|
||||
}
|
||||
if len(b)-start > 0 {
|
||||
l = len(b) - start
|
||||
packData(&t.buffer, b[start:start+l])
|
||||
}
|
||||
return t.buffer.Bytes(), nil
|
||||
case 1:
|
||||
if len(b) > 0 {
|
||||
if len(b) < 1024 {
|
||||
packData(&t.sendSaver, b)
|
||||
} else {
|
||||
start := 0
|
||||
var l int
|
||||
for len(b)-start > 2048 {
|
||||
l = rand.Intn(4096) + 100
|
||||
if l > len(b)-start {
|
||||
l = len(b) - start
|
||||
}
|
||||
packData(&t.buffer, b[start:start+l])
|
||||
start += l
|
||||
}
|
||||
if len(b)-start > 0 {
|
||||
l = len(b) - start
|
||||
packData(&t.buffer, b[start:start+l])
|
||||
}
|
||||
io.Copy(&t.sendSaver, &t.buffer)
|
||||
}
|
||||
return []byte{}, nil
|
||||
}
|
||||
hmacData := make([]byte, 43)
|
||||
handshakeFinish := []byte("\x14\x03\x03\x00\x01\x01\x16\x03\x03\x00\x20")
|
||||
copy(hmacData, handshakeFinish)
|
||||
rand.Read(hmacData[11:33])
|
||||
h := t.hmacSHA1(hmacData[:33])
|
||||
copy(hmacData[33:], h)
|
||||
t.buffer.Write(hmacData)
|
||||
io.Copy(&t.buffer, &t.sendSaver)
|
||||
t.handshakeStatus = 8
|
||||
return t.buffer.Bytes(), nil
|
||||
case 0:
|
||||
tlsData0 := []byte("\x00\x1c\xc0\x2b\xc0\x2f\xcc\xa9\xcc\xa8\xcc\x14\xcc\x13\xc0\x0a\xc0\x14\xc0\x09\xc0\x13\x00\x9c\x00\x35\x00\x2f\x00\x0a\x01\x00")
|
||||
tlsData1 := []byte("\xff\x01\x00\x01\x00")
|
||||
tlsData2 := []byte("\x00\x17\x00\x00\x00\x23\x00\xd0")
|
||||
// tlsData3 := []byte("\x00\x0d\x00\x16\x00\x14\x06\x01\x06\x03\x05\x01\x05\x03\x04\x01\x04\x03\x03\x01\x03\x03\x02\x01\x02\x03\x00\x05\x00\x05\x01\x00\x00\x00\x00\x00\x12\x00\x00\x75\x50\x00\x00\x00\x0b\x00\x02\x01\x00\x00\x0a\x00\x06\x00\x04\x00\x17\x00\x18\x00\x15\x00\x66\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
|
||||
tlsData3 := []byte("\x00\x0d\x00\x16\x00\x14\x06\x01\x06\x03\x05\x01\x05\x03\x04\x01\x04\x03\x03\x01\x03\x03\x02\x01\x02\x03\x00\x05\x00\x05\x01\x00\x00\x00\x00\x00\x12\x00\x00\x75\x50\x00\x00\x00\x0b\x00\x02\x01\x00\x00\x0a\x00\x06\x00\x04\x00\x17\x00\x18")
|
||||
|
||||
var tlsData [2048]byte
|
||||
tlsDataLen := 0
|
||||
copy(tlsData[0:], tlsData1)
|
||||
tlsDataLen += len(tlsData1)
|
||||
sni := t.sni(t.getHost())
|
||||
copy(tlsData[tlsDataLen:], sni)
|
||||
tlsDataLen += len(sni)
|
||||
copy(tlsData[tlsDataLen:], tlsData2)
|
||||
tlsDataLen += len(tlsData2)
|
||||
ticketLen := rand.Intn(164)*2 + 64
|
||||
tlsData[tlsDataLen-1] = uint8(ticketLen & 0xff)
|
||||
tlsData[tlsDataLen-2] = uint8(ticketLen >> 8)
|
||||
//ticketLen := 208
|
||||
rand.Read(tlsData[tlsDataLen : tlsDataLen+ticketLen])
|
||||
tlsDataLen += ticketLen
|
||||
copy(tlsData[tlsDataLen:], tlsData3)
|
||||
tlsDataLen += len(tlsData3)
|
||||
|
||||
length := 11 + 32 + 1 + 32 + len(tlsData0) + 2 + tlsDataLen
|
||||
encodedData := make([]byte, length)
|
||||
pdata := length - tlsDataLen
|
||||
l := tlsDataLen
|
||||
copy(encodedData[pdata:], tlsData[:tlsDataLen])
|
||||
encodedData[pdata-1] = uint8(tlsDataLen)
|
||||
encodedData[pdata-2] = uint8(tlsDataLen >> 8)
|
||||
pdata -= 2
|
||||
l += 2
|
||||
copy(encodedData[pdata-len(tlsData0):], tlsData0)
|
||||
pdata -= len(tlsData0)
|
||||
l += len(tlsData0)
|
||||
copy(encodedData[pdata-32:], t.localClientID[:])
|
||||
pdata -= 32
|
||||
l += 32
|
||||
encodedData[pdata-1] = 0x20
|
||||
pdata--
|
||||
l++
|
||||
copy(encodedData[pdata-32:], t.packAuthData())
|
||||
pdata -= 32
|
||||
l += 32
|
||||
encodedData[pdata-1] = 0x3
|
||||
encodedData[pdata-2] = 0x3 // tls version
|
||||
pdata -= 2
|
||||
l += 2
|
||||
encodedData[pdata-1] = uint8(l)
|
||||
encodedData[pdata-2] = uint8(l >> 8)
|
||||
encodedData[pdata-3] = 0
|
||||
encodedData[pdata-4] = 1
|
||||
pdata -= 4
|
||||
l += 4
|
||||
encodedData[pdata-1] = uint8(l)
|
||||
encodedData[pdata-2] = uint8(l >> 8)
|
||||
pdata -= 2
|
||||
l += 2
|
||||
encodedData[pdata-1] = 0x1
|
||||
encodedData[pdata-2] = 0x3 // tls version
|
||||
pdata -= 2
|
||||
l += 2
|
||||
encodedData[pdata-1] = 0x16 // tls handshake
|
||||
pdata--
|
||||
l++
|
||||
packData(&t.sendSaver, b)
|
||||
t.handshakeStatus = 1
|
||||
return encodedData, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected handshake status: %d", t.handshakeStatus)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *tls12Ticket) hmacSHA1(data []byte) []byte {
|
||||
key := make([]byte, len(t.Key)+32)
|
||||
copy(key, t.Key)
|
||||
copy(key[len(t.Key):], t.localClientID[:])
|
||||
|
||||
sha1Data := tools.HmacSHA1(key, data)
|
||||
return sha1Data[:tools.HmacSHA1Len]
|
||||
}
|
||||
|
||||
func (t *tls12Ticket) sni(u string) []byte {
|
||||
bURL := []byte(u)
|
||||
length := len(bURL)
|
||||
ret := make([]byte, length+9)
|
||||
copy(ret[9:9+length], bURL)
|
||||
binary.BigEndian.PutUint16(ret[7:], uint16(length&0xFFFF))
|
||||
length += 3
|
||||
binary.BigEndian.PutUint16(ret[4:], uint16(length&0xFFFF))
|
||||
length += 2
|
||||
binary.BigEndian.PutUint16(ret[2:], uint16(length&0xFFFF))
|
||||
return ret
|
||||
}
|
||||
|
||||
func (t *tls12Ticket) getHost() string {
|
||||
host := t.Host
|
||||
if len(t.Param) > 0 {
|
||||
hosts := strings.Split(t.Param, ",")
|
||||
if len(hosts) > 0 {
|
||||
|
||||
host = hosts[rand.Intn(len(hosts))]
|
||||
host = strings.TrimSpace(host)
|
||||
}
|
||||
}
|
||||
if len(host) > 0 && host[len(host)-1] >= byte('0') && host[len(host)-1] <= byte('9') && len(t.Param) == 0 {
|
||||
host = ""
|
||||
}
|
||||
return host
|
||||
}
|
||||
|
||||
func (t *tls12Ticket) packAuthData() (ret []byte) {
|
||||
retSize := 32
|
||||
ret = make([]byte, retSize)
|
||||
|
||||
now := time.Now().Unix()
|
||||
binary.BigEndian.PutUint32(ret[:4], uint32(now))
|
||||
|
||||
rand.Read(ret[4 : 4+18])
|
||||
|
||||
hash := t.hmacSHA1(ret[:retSize-tools.HmacSHA1Len])
|
||||
copy(ret[retSize-tools.HmacSHA1Len:], hash)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func packData(buffer *bytes.Buffer, suffix []byte) {
|
||||
d := []byte{0x17, 0x3, 0x3, 0, 0}
|
||||
binary.BigEndian.PutUint16(d[3:5], uint16(len(suffix)&0xFFFF))
|
||||
buffer.Write(d)
|
||||
buffer.Write(suffix)
|
||||
}
|
@ -1,310 +0,0 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
"github.com/Dreamacro/clash/component/ssr/tools"
|
||||
"github.com/Dreamacro/go-shadowsocks2/core"
|
||||
)
|
||||
|
||||
type authAES128 struct {
|
||||
*Base
|
||||
*recvInfo
|
||||
*authData
|
||||
hasSentHeader bool
|
||||
packID uint32
|
||||
userKey []byte
|
||||
uid [4]byte
|
||||
salt string
|
||||
hmac hmacMethod
|
||||
hashDigest hashDigestMethod
|
||||
}
|
||||
|
||||
func init() {
|
||||
register("auth_aes128_md5", newAuthAES128MD5)
|
||||
}
|
||||
|
||||
func newAuthAES128MD5(b *Base) Protocol {
|
||||
return &authAES128{
|
||||
Base: b,
|
||||
authData: &authData{},
|
||||
salt: "auth_aes128_md5",
|
||||
hmac: tools.HmacMD5,
|
||||
hashDigest: tools.MD5Sum,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *authAES128) initForConn(iv []byte) Protocol {
|
||||
return &authAES128{
|
||||
Base: &Base{
|
||||
IV: iv,
|
||||
Key: a.Key,
|
||||
TCPMss: a.TCPMss,
|
||||
Overhead: a.Overhead,
|
||||
Param: a.Param,
|
||||
},
|
||||
recvInfo: &recvInfo{recvID: 1, buffer: new(bytes.Buffer)},
|
||||
authData: a.authData,
|
||||
packID: 1,
|
||||
salt: a.salt,
|
||||
hmac: a.hmac,
|
||||
hashDigest: a.hashDigest,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *authAES128) GetProtocolOverhead() int {
|
||||
return 9
|
||||
}
|
||||
|
||||
func (a *authAES128) SetOverhead(overhead int) {
|
||||
a.Overhead = overhead
|
||||
}
|
||||
|
||||
func (a *authAES128) Decode(b []byte) ([]byte, int, error) {
|
||||
a.buffer.Reset()
|
||||
bSize := len(b)
|
||||
readSize := 0
|
||||
key := pool.Get(len(a.userKey) + 4)
|
||||
defer pool.Put(key)
|
||||
copy(key, a.userKey)
|
||||
for bSize > 4 {
|
||||
binary.LittleEndian.PutUint32(key[len(key)-4:], a.recvID)
|
||||
|
||||
h := a.hmac(key, b[:2])
|
||||
if !bytes.Equal(h[:2], b[2:4]) {
|
||||
return nil, 0, errAuthAES128IncorrectMAC
|
||||
}
|
||||
|
||||
length := int(binary.LittleEndian.Uint16(b[:2]))
|
||||
if length >= 8192 || length < 8 {
|
||||
return nil, 0, errAuthAES128DataLengthError
|
||||
}
|
||||
if length > bSize {
|
||||
break
|
||||
}
|
||||
|
||||
h = a.hmac(key, b[:length-4])
|
||||
if !bytes.Equal(h[:4], b[length-4:length]) {
|
||||
return nil, 0, errAuthAES128IncorrectChecksum
|
||||
}
|
||||
|
||||
a.recvID++
|
||||
pos := int(b[4])
|
||||
if pos < 255 {
|
||||
pos += 4
|
||||
} else {
|
||||
pos = int(binary.LittleEndian.Uint16(b[5:7])) + 4
|
||||
}
|
||||
|
||||
if pos > length-4 {
|
||||
return nil, 0, errAuthAES128PositionTooLarge
|
||||
}
|
||||
a.buffer.Write(b[pos : length-4])
|
||||
b = b[length:]
|
||||
bSize -= length
|
||||
readSize += length
|
||||
}
|
||||
return a.buffer.Bytes(), readSize, nil
|
||||
}
|
||||
|
||||
func (a *authAES128) Encode(b []byte) ([]byte, error) {
|
||||
a.buffer.Reset()
|
||||
bSize := len(b)
|
||||
offset := 0
|
||||
if bSize > 0 && !a.hasSentHeader {
|
||||
authSize := bSize
|
||||
if authSize > 1200 {
|
||||
authSize = 1200
|
||||
}
|
||||
a.hasSentHeader = true
|
||||
a.buffer.Write(a.packAuthData(b[:authSize]))
|
||||
bSize -= authSize
|
||||
offset += authSize
|
||||
}
|
||||
const blockSize = 4096
|
||||
for bSize > blockSize {
|
||||
packSize, randSize := a.packedDataSize(b[offset : offset+blockSize])
|
||||
pack := pool.Get(packSize)
|
||||
a.packData(b[offset:offset+blockSize], pack, randSize)
|
||||
a.buffer.Write(pack)
|
||||
pool.Put(pack)
|
||||
bSize -= blockSize
|
||||
offset += blockSize
|
||||
}
|
||||
if bSize > 0 {
|
||||
packSize, randSize := a.packedDataSize(b[offset:])
|
||||
pack := pool.Get(packSize)
|
||||
a.packData(b[offset:], pack, randSize)
|
||||
a.buffer.Write(pack)
|
||||
pool.Put(pack)
|
||||
}
|
||||
return a.buffer.Bytes(), nil
|
||||
}
|
||||
|
||||
func (a *authAES128) DecodePacket(b []byte) ([]byte, int, error) {
|
||||
bSize := len(b)
|
||||
h := a.hmac(a.Key, b[:bSize-4])
|
||||
if !bytes.Equal(h[:4], b[bSize-4:]) {
|
||||
return nil, 0, errAuthAES128IncorrectMAC
|
||||
}
|
||||
return b[:bSize-4], bSize - 4, nil
|
||||
}
|
||||
|
||||
func (a *authAES128) EncodePacket(b []byte) ([]byte, error) {
|
||||
a.initUserKeyAndID()
|
||||
|
||||
var buf bytes.Buffer
|
||||
buf.Write(b)
|
||||
buf.Write(a.uid[:])
|
||||
h := a.hmac(a.userKey, buf.Bytes())
|
||||
buf.Write(h[:4])
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (a *authAES128) initUserKeyAndID() {
|
||||
if a.userKey == nil {
|
||||
params := strings.Split(a.Param, ":")
|
||||
if len(params) >= 2 {
|
||||
if userID, err := strconv.ParseUint(params[0], 10, 32); err == nil {
|
||||
binary.LittleEndian.PutUint32(a.uid[:], uint32(userID))
|
||||
a.userKey = a.hashDigest([]byte(params[1]))
|
||||
}
|
||||
}
|
||||
|
||||
if a.userKey == nil {
|
||||
rand.Read(a.uid[:])
|
||||
a.userKey = make([]byte, len(a.Key))
|
||||
copy(a.userKey, a.Key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *authAES128) packedDataSize(data []byte) (packSize, randSize int) {
|
||||
dataSize := len(data)
|
||||
randSize = 1
|
||||
if dataSize <= 1200 {
|
||||
if a.packID > 4 {
|
||||
randSize += rand.Intn(32)
|
||||
} else {
|
||||
if dataSize > 900 {
|
||||
randSize += rand.Intn(128)
|
||||
} else {
|
||||
randSize += rand.Intn(512)
|
||||
}
|
||||
}
|
||||
}
|
||||
packSize = randSize + dataSize + 8
|
||||
return
|
||||
}
|
||||
|
||||
func (a *authAES128) packData(data, ret []byte, randSize int) {
|
||||
dataSize := len(data)
|
||||
retSize := len(ret)
|
||||
// 0~1, ret_size
|
||||
binary.LittleEndian.PutUint16(ret[0:], uint16(retSize&0xFFFF))
|
||||
// 2~3, hmac
|
||||
key := pool.Get(len(a.userKey) + 4)
|
||||
defer pool.Put(key)
|
||||
copy(key, a.userKey)
|
||||
binary.LittleEndian.PutUint32(key[len(key)-4:], a.packID)
|
||||
h := a.hmac(key, ret[:2])
|
||||
copy(ret[2:4], h[:2])
|
||||
// 4~rand_size+4, rand number
|
||||
rand.Read(ret[4 : 4+randSize])
|
||||
// 4, rand_size
|
||||
if randSize < 128 {
|
||||
ret[4] = byte(randSize & 0xFF)
|
||||
} else {
|
||||
// 4, magic number 0xFF
|
||||
ret[4] = 0xFF
|
||||
// 5~6, rand_size
|
||||
binary.LittleEndian.PutUint16(ret[5:], uint16(randSize&0xFFFF))
|
||||
}
|
||||
// rand_size+4~ret_size-4, data
|
||||
if dataSize > 0 {
|
||||
copy(ret[randSize+4:], data)
|
||||
}
|
||||
a.packID++
|
||||
h = a.hmac(key, ret[:retSize-4])
|
||||
copy(ret[retSize-4:], h[:4])
|
||||
}
|
||||
|
||||
func (a *authAES128) packAuthData(data []byte) (ret []byte) {
|
||||
dataSize := len(data)
|
||||
var randSize int
|
||||
|
||||
if dataSize > 400 {
|
||||
randSize = rand.Intn(512)
|
||||
} else {
|
||||
randSize = rand.Intn(1024)
|
||||
}
|
||||
|
||||
dataOffset := randSize + 16 + 4 + 4 + 7
|
||||
retSize := dataOffset + dataSize + 4
|
||||
ret = make([]byte, retSize)
|
||||
encrypt := make([]byte, 24)
|
||||
key := make([]byte, len(a.IV)+len(a.Key))
|
||||
copy(key, a.IV)
|
||||
copy(key[len(a.IV):], a.Key)
|
||||
|
||||
rand.Read(ret[dataOffset-randSize:])
|
||||
a.mutex.Lock()
|
||||
defer a.mutex.Unlock()
|
||||
a.connectionID++
|
||||
if a.connectionID > 0xFF000000 {
|
||||
a.clientID = nil
|
||||
}
|
||||
if len(a.clientID) == 0 {
|
||||
a.clientID = make([]byte, 8)
|
||||
rand.Read(a.clientID)
|
||||
b := make([]byte, 4)
|
||||
rand.Read(b)
|
||||
a.connectionID = binary.LittleEndian.Uint32(b) & 0xFFFFFF
|
||||
}
|
||||
copy(encrypt[4:], a.clientID)
|
||||
binary.LittleEndian.PutUint32(encrypt[8:], a.connectionID)
|
||||
|
||||
now := time.Now().Unix()
|
||||
binary.LittleEndian.PutUint32(encrypt[:4], uint32(now))
|
||||
|
||||
binary.LittleEndian.PutUint16(encrypt[12:], uint16(retSize&0xFFFF))
|
||||
binary.LittleEndian.PutUint16(encrypt[14:], uint16(randSize&0xFFFF))
|
||||
|
||||
a.initUserKeyAndID()
|
||||
|
||||
aesCipherKey := core.Kdf(base64.StdEncoding.EncodeToString(a.userKey)+a.salt, 16)
|
||||
block, err := aes.NewCipher(aesCipherKey)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
encryptData := make([]byte, 16)
|
||||
iv := make([]byte, aes.BlockSize)
|
||||
cbc := cipher.NewCBCEncrypter(block, iv)
|
||||
cbc.CryptBlocks(encryptData, encrypt[:16])
|
||||
copy(encrypt[:4], a.uid[:])
|
||||
copy(encrypt[4:4+16], encryptData)
|
||||
|
||||
h := a.hmac(key, encrypt[:20])
|
||||
copy(encrypt[20:], h[:4])
|
||||
|
||||
rand.Read(ret[:1])
|
||||
h = a.hmac(key, ret[:1])
|
||||
copy(ret[1:], h[:7-1])
|
||||
|
||||
copy(ret[7:], encrypt)
|
||||
copy(ret[dataOffset:], data)
|
||||
|
||||
h = a.hmac(a.userKey, ret[:retSize-4])
|
||||
copy(ret[retSize-4:], h[:4])
|
||||
|
||||
return
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/Dreamacro/clash/component/ssr/tools"
|
||||
)
|
||||
|
||||
func init() {
|
||||
register("auth_aes128_sha1", newAuthAES128SHA1)
|
||||
}
|
||||
|
||||
func newAuthAES128SHA1(b *Base) Protocol {
|
||||
return &authAES128{
|
||||
Base: b,
|
||||
recvInfo: &recvInfo{buffer: new(bytes.Buffer)},
|
||||
authData: &authData{},
|
||||
salt: "auth_aes128_sha1",
|
||||
hmac: tools.HmacSHA1,
|
||||
hashDigest: tools.SHA1Sum,
|
||||
}
|
||||
}
|
@ -1,427 +0,0 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rc4"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
"github.com/Dreamacro/clash/component/ssr/tools"
|
||||
"github.com/Dreamacro/go-shadowsocks2/core"
|
||||
)
|
||||
|
||||
type authChain struct {
|
||||
*Base
|
||||
*recvInfo
|
||||
*authData
|
||||
randomClient shift128PlusContext
|
||||
randomServer shift128PlusContext
|
||||
enc cipher.Stream
|
||||
dec cipher.Stream
|
||||
headerSent bool
|
||||
lastClientHash []byte
|
||||
lastServerHash []byte
|
||||
userKey []byte
|
||||
uid [4]byte
|
||||
salt string
|
||||
hmac hmacMethod
|
||||
hashDigest hashDigestMethod
|
||||
rnd rndMethod
|
||||
dataSizeList []int
|
||||
dataSizeList2 []int
|
||||
chunkID uint32
|
||||
}
|
||||
|
||||
func init() {
|
||||
register("auth_chain_a", newAuthChainA)
|
||||
}
|
||||
|
||||
func newAuthChainA(b *Base) Protocol {
|
||||
return &authChain{
|
||||
Base: b,
|
||||
authData: &authData{},
|
||||
salt: "auth_chain_a",
|
||||
hmac: tools.HmacMD5,
|
||||
hashDigest: tools.SHA1Sum,
|
||||
rnd: authChainAGetRandLen,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *authChain) initForConn(iv []byte) Protocol {
|
||||
r := &authChain{
|
||||
Base: &Base{
|
||||
IV: iv,
|
||||
Key: a.Key,
|
||||
TCPMss: a.TCPMss,
|
||||
Overhead: a.Overhead,
|
||||
Param: a.Param,
|
||||
},
|
||||
recvInfo: &recvInfo{recvID: 1, buffer: new(bytes.Buffer)},
|
||||
authData: a.authData,
|
||||
salt: a.salt,
|
||||
hmac: a.hmac,
|
||||
hashDigest: a.hashDigest,
|
||||
rnd: a.rnd,
|
||||
}
|
||||
if r.salt == "auth_chain_b" {
|
||||
initDataSize(r)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (a *authChain) GetProtocolOverhead() int {
|
||||
return 4
|
||||
}
|
||||
|
||||
func (a *authChain) SetOverhead(overhead int) {
|
||||
a.Overhead = overhead
|
||||
}
|
||||
|
||||
func (a *authChain) Decode(b []byte) ([]byte, int, error) {
|
||||
a.buffer.Reset()
|
||||
key := pool.Get(len(a.userKey) + 4)
|
||||
defer pool.Put(key)
|
||||
readSize := 0
|
||||
copy(key, a.userKey)
|
||||
for len(b) > 4 {
|
||||
binary.LittleEndian.PutUint32(key[len(a.userKey):], a.recvID)
|
||||
dataLen := (int)((uint(b[1]^a.lastServerHash[15]) << 8) + uint(b[0]^a.lastServerHash[14]))
|
||||
randLen := a.getServerRandLen(dataLen, a.Overhead)
|
||||
length := randLen + dataLen
|
||||
if length >= 4096 {
|
||||
return nil, 0, errAuthChainDataLengthError
|
||||
}
|
||||
length += 4
|
||||
if length > len(b) {
|
||||
break
|
||||
}
|
||||
|
||||
hash := a.hmac(key, b[:length-2])
|
||||
if !bytes.Equal(hash[:2], b[length-2:length]) {
|
||||
return nil, 0, errAuthChainHMACError
|
||||
}
|
||||
var dataPos int
|
||||
if dataLen > 0 && randLen > 0 {
|
||||
dataPos = 2 + getRandStartPos(&a.randomServer, randLen)
|
||||
} else {
|
||||
dataPos = 2
|
||||
}
|
||||
d := pool.Get(dataLen)
|
||||
a.dec.XORKeyStream(d, b[dataPos:dataPos+dataLen])
|
||||
a.buffer.Write(d)
|
||||
pool.Put(d)
|
||||
if a.recvID == 1 {
|
||||
a.TCPMss = int(binary.LittleEndian.Uint16(a.buffer.Next(2)))
|
||||
}
|
||||
a.lastServerHash = hash
|
||||
a.recvID++
|
||||
b = b[length:]
|
||||
readSize += length
|
||||
}
|
||||
return a.buffer.Bytes(), readSize, nil
|
||||
}
|
||||
|
||||
func (a *authChain) Encode(b []byte) ([]byte, error) {
|
||||
a.buffer.Reset()
|
||||
bSize := len(b)
|
||||
offset := 0
|
||||
if bSize > 0 && !a.headerSent {
|
||||
headSize := 1200
|
||||
if headSize > bSize {
|
||||
headSize = bSize
|
||||
}
|
||||
a.buffer.Write(a.packAuthData(b[:headSize]))
|
||||
offset += headSize
|
||||
bSize -= headSize
|
||||
a.headerSent = true
|
||||
}
|
||||
var unitSize = a.TCPMss - a.Overhead
|
||||
for bSize > unitSize {
|
||||
dataLen, randLength := a.packedDataLen(b[offset : offset+unitSize])
|
||||
d := pool.Get(dataLen)
|
||||
a.packData(d, b[offset:offset+unitSize], randLength)
|
||||
a.buffer.Write(d)
|
||||
pool.Put(d)
|
||||
bSize -= unitSize
|
||||
offset += unitSize
|
||||
}
|
||||
if bSize > 0 {
|
||||
dataLen, randLength := a.packedDataLen(b[offset:])
|
||||
d := pool.Get(dataLen)
|
||||
a.packData(d, b[offset:], randLength)
|
||||
a.buffer.Write(d)
|
||||
pool.Put(d)
|
||||
}
|
||||
return a.buffer.Bytes(), nil
|
||||
}
|
||||
|
||||
func (a *authChain) DecodePacket(b []byte) ([]byte, int, error) {
|
||||
bSize := len(b)
|
||||
if bSize < 9 {
|
||||
return nil, 0, errAuthChainDataLengthError
|
||||
}
|
||||
h := a.hmac(a.userKey, b[:bSize-1])
|
||||
if h[0] != b[bSize-1] {
|
||||
return nil, 0, errAuthChainHMACError
|
||||
}
|
||||
hash := a.hmac(a.Key, b[bSize-8:bSize-1])
|
||||
cipherKey := a.getRC4CipherKey(hash)
|
||||
dec, _ := rc4.NewCipher(cipherKey)
|
||||
randLength := udpGetRandLen(&a.randomServer, hash)
|
||||
bSize -= 8 + randLength
|
||||
dec.XORKeyStream(b, b[:bSize])
|
||||
return b, bSize, nil
|
||||
}
|
||||
|
||||
func (a *authChain) EncodePacket(b []byte) ([]byte, error) {
|
||||
a.initUserKeyAndID()
|
||||
authData := pool.Get(3)
|
||||
defer pool.Put(authData)
|
||||
rand.Read(authData)
|
||||
hash := a.hmac(a.Key, authData)
|
||||
uid := pool.Get(4)
|
||||
defer pool.Put(uid)
|
||||
for i := 0; i < 4; i++ {
|
||||
uid[i] = a.uid[i] ^ hash[i]
|
||||
}
|
||||
|
||||
cipherKey := a.getRC4CipherKey(hash)
|
||||
enc, _ := rc4.NewCipher(cipherKey)
|
||||
var buf bytes.Buffer
|
||||
enc.XORKeyStream(b, b)
|
||||
buf.Write(b)
|
||||
|
||||
randLength := udpGetRandLen(&a.randomClient, hash)
|
||||
randBytes := pool.Get(randLength)
|
||||
defer pool.Put(randBytes)
|
||||
buf.Write(randBytes)
|
||||
|
||||
buf.Write(authData)
|
||||
buf.Write(uid)
|
||||
|
||||
h := a.hmac(a.userKey, buf.Bytes())
|
||||
buf.Write(h[:1])
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (a *authChain) getRC4CipherKey(hash []byte) []byte {
|
||||
base64UserKey := base64.StdEncoding.EncodeToString(a.userKey)
|
||||
return a.calcRC4CipherKey(hash, base64UserKey)
|
||||
}
|
||||
|
||||
func (a *authChain) calcRC4CipherKey(hash []byte, base64UserKey string) []byte {
|
||||
password := pool.Get(len(base64UserKey) + base64.StdEncoding.EncodedLen(16))
|
||||
defer pool.Put(password)
|
||||
copy(password, base64UserKey)
|
||||
base64.StdEncoding.Encode(password[len(base64UserKey):], hash[:16])
|
||||
return core.Kdf(string(password), 16)
|
||||
}
|
||||
|
||||
func (a *authChain) initUserKeyAndID() {
|
||||
if a.userKey == nil {
|
||||
params := strings.Split(a.Param, ":")
|
||||
if len(params) >= 2 {
|
||||
if userID, err := strconv.ParseUint(params[0], 10, 32); err == nil {
|
||||
binary.LittleEndian.PutUint32(a.uid[:], uint32(userID))
|
||||
a.userKey = []byte(params[1])[:len(a.userKey)]
|
||||
}
|
||||
}
|
||||
|
||||
if a.userKey == nil {
|
||||
rand.Read(a.uid[:])
|
||||
a.userKey = make([]byte, len(a.Key))
|
||||
copy(a.userKey, a.Key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *authChain) getClientRandLen(dataLength int, overhead int) int {
|
||||
return a.rnd(dataLength, &a.randomClient, a.lastClientHash, a.dataSizeList, a.dataSizeList2, overhead)
|
||||
}
|
||||
|
||||
func (a *authChain) getServerRandLen(dataLength int, overhead int) int {
|
||||
return a.rnd(dataLength, &a.randomServer, a.lastServerHash, a.dataSizeList, a.dataSizeList2, overhead)
|
||||
}
|
||||
|
||||
func (a *authChain) packedDataLen(data []byte) (chunkLength, randLength int) {
|
||||
dataLength := len(data)
|
||||
randLength = a.getClientRandLen(dataLength, a.Overhead)
|
||||
chunkLength = randLength + dataLength + 2 + 2
|
||||
return
|
||||
}
|
||||
|
||||
func (a *authChain) packData(outData []byte, data []byte, randLength int) {
|
||||
dataLength := len(data)
|
||||
outLength := randLength + dataLength + 2
|
||||
outData[0] = byte(dataLength) ^ a.lastClientHash[14]
|
||||
outData[1] = byte(dataLength>>8) ^ a.lastClientHash[15]
|
||||
|
||||
{
|
||||
if dataLength > 0 {
|
||||
randPart1Length := getRandStartPos(&a.randomClient, randLength)
|
||||
rand.Read(outData[2 : 2+randPart1Length])
|
||||
a.enc.XORKeyStream(outData[2+randPart1Length:], data)
|
||||
rand.Read(outData[2+randPart1Length+dataLength : outLength])
|
||||
} else {
|
||||
rand.Read(outData[2 : 2+randLength])
|
||||
}
|
||||
}
|
||||
|
||||
userKeyLen := uint8(len(a.userKey))
|
||||
key := pool.Get(int(userKeyLen + 4))
|
||||
defer pool.Put(key)
|
||||
copy(key, a.userKey)
|
||||
a.chunkID++
|
||||
binary.LittleEndian.PutUint32(key[userKeyLen:], a.chunkID)
|
||||
a.lastClientHash = a.hmac(key, outData[:outLength])
|
||||
copy(outData[outLength:], a.lastClientHash[:2])
|
||||
}
|
||||
|
||||
const authHeadLength = 4 + 8 + 4 + 16 + 4
|
||||
|
||||
func (a *authChain) packAuthData(data []byte) (outData []byte) {
|
||||
outData = make([]byte, authHeadLength, authHeadLength+1500)
|
||||
a.mutex.Lock()
|
||||
defer a.mutex.Unlock()
|
||||
a.connectionID++
|
||||
if a.connectionID > 0xFF000000 {
|
||||
rand.Read(a.clientID)
|
||||
b := make([]byte, 4)
|
||||
rand.Read(b)
|
||||
a.connectionID = binary.LittleEndian.Uint32(b) & 0xFFFFFF
|
||||
}
|
||||
var key = make([]byte, len(a.IV)+len(a.Key))
|
||||
copy(key, a.IV)
|
||||
copy(key[len(a.IV):], a.Key)
|
||||
|
||||
encrypt := make([]byte, 20)
|
||||
t := time.Now().Unix()
|
||||
binary.LittleEndian.PutUint32(encrypt[:4], uint32(t))
|
||||
copy(encrypt[4:8], a.clientID)
|
||||
binary.LittleEndian.PutUint32(encrypt[8:], a.connectionID)
|
||||
binary.LittleEndian.PutUint16(encrypt[12:], uint16(a.Overhead))
|
||||
binary.LittleEndian.PutUint16(encrypt[14:], 0)
|
||||
|
||||
// first 12 bytes
|
||||
{
|
||||
rand.Read(outData[:4])
|
||||
a.lastClientHash = a.hmac(key, outData[:4])
|
||||
copy(outData[4:], a.lastClientHash[:8])
|
||||
}
|
||||
var base64UserKey string
|
||||
// uid & 16 bytes auth data
|
||||
{
|
||||
a.initUserKeyAndID()
|
||||
uid := make([]byte, 4)
|
||||
for i := 0; i < 4; i++ {
|
||||
uid[i] = a.uid[i] ^ a.lastClientHash[8+i]
|
||||
}
|
||||
base64UserKey = base64.StdEncoding.EncodeToString(a.userKey)
|
||||
aesCipherKey := core.Kdf(base64UserKey+a.salt, 16)
|
||||
block, err := aes.NewCipher(aesCipherKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
encryptData := make([]byte, 16)
|
||||
iv := make([]byte, aes.BlockSize)
|
||||
cbc := cipher.NewCBCEncrypter(block, iv)
|
||||
cbc.CryptBlocks(encryptData, encrypt[:16])
|
||||
copy(encrypt[:4], uid[:])
|
||||
copy(encrypt[4:4+16], encryptData)
|
||||
}
|
||||
// final HMAC
|
||||
{
|
||||
a.lastServerHash = a.hmac(a.userKey, encrypt[:20])
|
||||
|
||||
copy(outData[12:], encrypt)
|
||||
copy(outData[12+20:], a.lastServerHash[:4])
|
||||
}
|
||||
|
||||
// init cipher
|
||||
cipherKey := a.calcRC4CipherKey(a.lastClientHash, base64UserKey)
|
||||
a.enc, _ = rc4.NewCipher(cipherKey)
|
||||
a.dec, _ = rc4.NewCipher(cipherKey)
|
||||
|
||||
// data
|
||||
chunkLength, randLength := a.packedDataLen(data)
|
||||
if chunkLength <= 1500 {
|
||||
outData = outData[:authHeadLength+chunkLength]
|
||||
} else {
|
||||
newOutData := make([]byte, authHeadLength+chunkLength)
|
||||
copy(newOutData, outData[:authHeadLength])
|
||||
outData = newOutData
|
||||
}
|
||||
a.packData(outData[authHeadLength:], data, randLength)
|
||||
return
|
||||
}
|
||||
|
||||
func getRandStartPos(random *shift128PlusContext, randLength int) int {
|
||||
if randLength > 0 {
|
||||
return int(random.Next() % 8589934609 % uint64(randLength))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func authChainAGetRandLen(dataLength int, random *shift128PlusContext, lastHash []byte, dataSizeList, dataSizeList2 []int, overhead int) int {
|
||||
if dataLength > 1440 {
|
||||
return 0
|
||||
}
|
||||
random.InitFromBinDatalen(lastHash[:16], dataLength)
|
||||
if dataLength > 1300 {
|
||||
return int(random.Next() % 31)
|
||||
}
|
||||
if dataLength > 900 {
|
||||
return int(random.Next() % 127)
|
||||
}
|
||||
if dataLength > 400 {
|
||||
return int(random.Next() % 521)
|
||||
}
|
||||
return int(random.Next() % 1021)
|
||||
}
|
||||
|
||||
func udpGetRandLen(random *shift128PlusContext, lastHash []byte) int {
|
||||
random.InitFromBin(lastHash[:16])
|
||||
return int(random.Next() % 127)
|
||||
}
|
||||
|
||||
type shift128PlusContext struct {
|
||||
v [2]uint64
|
||||
}
|
||||
|
||||
func (ctx *shift128PlusContext) InitFromBin(bin []byte) {
|
||||
var fillBin [16]byte
|
||||
copy(fillBin[:], bin)
|
||||
|
||||
ctx.v[0] = binary.LittleEndian.Uint64(fillBin[:8])
|
||||
ctx.v[1] = binary.LittleEndian.Uint64(fillBin[8:])
|
||||
}
|
||||
|
||||
func (ctx *shift128PlusContext) InitFromBinDatalen(bin []byte, datalen int) {
|
||||
var fillBin [16]byte
|
||||
copy(fillBin[:], bin)
|
||||
binary.LittleEndian.PutUint16(fillBin[:2], uint16(datalen))
|
||||
|
||||
ctx.v[0] = binary.LittleEndian.Uint64(fillBin[:8])
|
||||
ctx.v[1] = binary.LittleEndian.Uint64(fillBin[8:])
|
||||
|
||||
for i := 0; i < 4; i++ {
|
||||
ctx.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func (ctx *shift128PlusContext) Next() uint64 {
|
||||
x := ctx.v[0]
|
||||
y := ctx.v[1]
|
||||
ctx.v[0] = y
|
||||
x ^= x << 23
|
||||
x ^= y ^ (x >> 17) ^ (y >> 26)
|
||||
ctx.v[1] = x
|
||||
return x + y
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/Dreamacro/clash/component/ssr/tools"
|
||||
)
|
||||
|
||||
func init() {
|
||||
register("auth_chain_b", newAuthChainB)
|
||||
}
|
||||
|
||||
func newAuthChainB(b *Base) Protocol {
|
||||
return &authChain{
|
||||
Base: b,
|
||||
authData: &authData{},
|
||||
salt: "auth_chain_b",
|
||||
hmac: tools.HmacMD5,
|
||||
hashDigest: tools.SHA1Sum,
|
||||
rnd: authChainBGetRandLen,
|
||||
}
|
||||
}
|
||||
|
||||
func initDataSize(r *authChain) {
|
||||
random := &r.randomServer
|
||||
random.InitFromBin(r.Key)
|
||||
len := random.Next()%8 + 4
|
||||
r.dataSizeList = make([]int, len)
|
||||
for i := 0; i < int(len); i++ {
|
||||
r.dataSizeList[i] = int(random.Next() % 2340 % 2040 % 1440)
|
||||
}
|
||||
sort.Ints(r.dataSizeList)
|
||||
|
||||
len = random.Next()%16 + 8
|
||||
r.dataSizeList2 = make([]int, len)
|
||||
for i := 0; i < int(len); i++ {
|
||||
r.dataSizeList2[i] = int(random.Next() % 2340 % 2040 % 1440)
|
||||
}
|
||||
sort.Ints(r.dataSizeList2)
|
||||
}
|
||||
|
||||
func authChainBGetRandLen(dataLength int, random *shift128PlusContext, lastHash []byte, dataSizeList, dataSizeList2 []int, overhead int) int {
|
||||
if dataLength > 1440 {
|
||||
return 0
|
||||
}
|
||||
random.InitFromBinDatalen(lastHash[:16], dataLength)
|
||||
pos := sort.Search(len(dataSizeList), func(i int) bool { return dataSizeList[i] > dataLength+overhead })
|
||||
finalPos := uint64(pos) + random.Next()%uint64(len(dataSizeList))
|
||||
if finalPos < uint64(len(dataSizeList)) {
|
||||
return dataSizeList[finalPos] - dataLength - overhead
|
||||
}
|
||||
|
||||
pos = sort.Search(len(dataSizeList2), func(i int) bool { return dataSizeList2[i] > dataLength+overhead })
|
||||
finalPos = uint64(pos) + random.Next()%uint64(len(dataSizeList2))
|
||||
if finalPos < uint64(len(dataSizeList2)) {
|
||||
return dataSizeList2[finalPos] - dataLength - overhead
|
||||
}
|
||||
if finalPos < uint64(pos+len(dataSizeList2)-1) {
|
||||
return 0
|
||||
}
|
||||
|
||||
if dataLength > 1300 {
|
||||
return int(random.Next() % 31)
|
||||
}
|
||||
if dataLength > 900 {
|
||||
return int(random.Next() % 127)
|
||||
}
|
||||
if dataLength > 400 {
|
||||
return int(random.Next() % 521)
|
||||
}
|
||||
return int(random.Next() % 1021)
|
||||
}
|
@ -1,253 +0,0 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"hash/adler32"
|
||||
"hash/crc32"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
"github.com/Dreamacro/clash/component/ssr/tools"
|
||||
)
|
||||
|
||||
type authSHA1V4 struct {
|
||||
*Base
|
||||
*authData
|
||||
headerSent bool
|
||||
buffer bytes.Buffer
|
||||
}
|
||||
|
||||
func init() {
|
||||
register("auth_sha1_v4", newAuthSHA1V4)
|
||||
}
|
||||
|
||||
func newAuthSHA1V4(b *Base) Protocol {
|
||||
return &authSHA1V4{Base: b, authData: &authData{}}
|
||||
}
|
||||
|
||||
func (a *authSHA1V4) initForConn(iv []byte) Protocol {
|
||||
return &authSHA1V4{
|
||||
Base: &Base{
|
||||
IV: iv,
|
||||
Key: a.Key,
|
||||
TCPMss: a.TCPMss,
|
||||
Overhead: a.Overhead,
|
||||
Param: a.Param,
|
||||
},
|
||||
authData: a.authData,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *authSHA1V4) GetProtocolOverhead() int {
|
||||
return 7
|
||||
}
|
||||
|
||||
func (a *authSHA1V4) SetOverhead(overhead int) {
|
||||
a.Overhead = overhead
|
||||
}
|
||||
|
||||
func (a *authSHA1V4) Decode(b []byte) ([]byte, int, error) {
|
||||
a.buffer.Reset()
|
||||
bSize := len(b)
|
||||
originalSize := bSize
|
||||
for bSize > 4 {
|
||||
crc := crc32.ChecksumIEEE(b[:2]) & 0xFFFF
|
||||
if binary.LittleEndian.Uint16(b[2:4]) != uint16(crc) {
|
||||
return nil, 0, errAuthSHA1v4CRC32Error
|
||||
}
|
||||
length := int(binary.BigEndian.Uint16(b[:2]))
|
||||
if length >= 8192 || length < 8 {
|
||||
return nil, 0, errAuthSHA1v4DataLengthError
|
||||
}
|
||||
if length > bSize {
|
||||
break
|
||||
}
|
||||
|
||||
if adler32.Checksum(b[:length-4]) == binary.LittleEndian.Uint32(b[length-4:]) {
|
||||
pos := int(b[4])
|
||||
if pos != 0xFF {
|
||||
pos += 4
|
||||
} else {
|
||||
pos = int(binary.BigEndian.Uint16(b[5:5+2])) + 4
|
||||
}
|
||||
retSize := length - pos - 4
|
||||
a.buffer.Write(b[pos : pos+retSize])
|
||||
bSize -= length
|
||||
b = b[length:]
|
||||
} else {
|
||||
return nil, 0, errAuthSHA1v4IncorrectChecksum
|
||||
}
|
||||
}
|
||||
return a.buffer.Bytes(), originalSize - bSize, nil
|
||||
}
|
||||
|
||||
func (a *authSHA1V4) Encode(b []byte) ([]byte, error) {
|
||||
a.buffer.Reset()
|
||||
bSize := len(b)
|
||||
offset := 0
|
||||
if !a.headerSent && bSize > 0 {
|
||||
headSize := getHeadSize(b, 30)
|
||||
if headSize > bSize {
|
||||
headSize = bSize
|
||||
}
|
||||
a.buffer.Write(a.packAuthData(b[:headSize]))
|
||||
offset += headSize
|
||||
bSize -= headSize
|
||||
a.headerSent = true
|
||||
}
|
||||
const blockSize = 4096
|
||||
for bSize > blockSize {
|
||||
packSize, randSize := a.packedDataSize(b[offset : offset+blockSize])
|
||||
pack := pool.Get(packSize)
|
||||
a.packData(b[offset:offset+blockSize], pack, randSize)
|
||||
a.buffer.Write(pack)
|
||||
pool.Put(pack)
|
||||
offset += blockSize
|
||||
bSize -= blockSize
|
||||
}
|
||||
if bSize > 0 {
|
||||
packSize, randSize := a.packedDataSize(b[offset:])
|
||||
pack := pool.Get(packSize)
|
||||
a.packData(b[offset:], pack, randSize)
|
||||
a.buffer.Write(pack)
|
||||
pool.Put(pack)
|
||||
}
|
||||
return a.buffer.Bytes(), nil
|
||||
}
|
||||
|
||||
func (a *authSHA1V4) DecodePacket(b []byte) ([]byte, int, error) {
|
||||
return b, len(b), nil
|
||||
}
|
||||
|
||||
func (a *authSHA1V4) EncodePacket(b []byte) ([]byte, error) {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (a *authSHA1V4) packedDataSize(data []byte) (packSize, randSize int) {
|
||||
dataSize := len(data)
|
||||
randSize = 1
|
||||
if dataSize <= 1300 {
|
||||
if dataSize > 400 {
|
||||
randSize += rand.Intn(128)
|
||||
} else {
|
||||
randSize += rand.Intn(1024)
|
||||
}
|
||||
}
|
||||
packSize = randSize + dataSize + 8
|
||||
return
|
||||
}
|
||||
|
||||
func (a *authSHA1V4) packData(data, ret []byte, randSize int) {
|
||||
dataSize := len(data)
|
||||
retSize := len(ret)
|
||||
// 0~1, ret size
|
||||
binary.BigEndian.PutUint16(ret[:2], uint16(retSize&0xFFFF))
|
||||
// 2~3, crc of ret size
|
||||
crc := crc32.ChecksumIEEE(ret[:2]) & 0xFFFF
|
||||
binary.LittleEndian.PutUint16(ret[2:4], uint16(crc))
|
||||
// 4, rand size
|
||||
if randSize < 128 {
|
||||
ret[4] = uint8(randSize & 0xFF)
|
||||
} else {
|
||||
ret[4] = uint8(0xFF)
|
||||
binary.BigEndian.PutUint16(ret[5:7], uint16(randSize&0xFFFF))
|
||||
}
|
||||
// (rand size+4)~(ret size-4), data
|
||||
if dataSize > 0 {
|
||||
copy(ret[randSize+4:], data)
|
||||
}
|
||||
// (ret size-4)~end, adler32 of full data
|
||||
adler := adler32.Checksum(ret[:retSize-4])
|
||||
binary.LittleEndian.PutUint32(ret[retSize-4:], adler)
|
||||
}
|
||||
|
||||
func (a *authSHA1V4) packAuthData(data []byte) (ret []byte) {
|
||||
dataSize := len(data)
|
||||
randSize := 1
|
||||
if dataSize <= 1300 {
|
||||
if dataSize > 400 {
|
||||
randSize += rand.Intn(128)
|
||||
} else {
|
||||
randSize += rand.Intn(1024)
|
||||
}
|
||||
}
|
||||
dataOffset := randSize + 4 + 2
|
||||
retSize := dataOffset + dataSize + 12 + tools.HmacSHA1Len
|
||||
ret = make([]byte, retSize)
|
||||
a.mutex.Lock()
|
||||
defer a.mutex.Unlock()
|
||||
a.connectionID++
|
||||
if a.connectionID > 0xFF000000 {
|
||||
a.clientID = nil
|
||||
}
|
||||
if len(a.clientID) == 0 {
|
||||
a.clientID = make([]byte, 8)
|
||||
rand.Read(a.clientID)
|
||||
b := make([]byte, 4)
|
||||
rand.Read(b)
|
||||
a.connectionID = binary.LittleEndian.Uint32(b) & 0xFFFFFF
|
||||
}
|
||||
// 0~1, ret size
|
||||
binary.BigEndian.PutUint16(ret[:2], uint16(retSize&0xFFFF))
|
||||
|
||||
// 2~6, crc of (ret size+salt+key)
|
||||
salt := []byte("auth_sha1_v4")
|
||||
crcData := make([]byte, len(salt)+len(a.Key)+2)
|
||||
copy(crcData[:2], ret[:2])
|
||||
copy(crcData[2:], salt)
|
||||
copy(crcData[2+len(salt):], a.Key)
|
||||
crc := crc32.ChecksumIEEE(crcData) & 0xFFFFFFFF
|
||||
// 2~6, crc of (ret size+salt+key)
|
||||
binary.LittleEndian.PutUint32(ret[2:], crc)
|
||||
// 6~(rand size+6), rand numbers
|
||||
rand.Read(ret[dataOffset-randSize : dataOffset])
|
||||
// 6, rand size
|
||||
if randSize < 128 {
|
||||
ret[6] = byte(randSize & 0xFF)
|
||||
} else {
|
||||
// 6, magic number 0xFF
|
||||
ret[6] = 0xFF
|
||||
// 7~8, rand size
|
||||
binary.BigEndian.PutUint16(ret[7:9], uint16(randSize&0xFFFF))
|
||||
}
|
||||
// rand size+6~(rand size+10), time stamp
|
||||
now := time.Now().Unix()
|
||||
binary.LittleEndian.PutUint32(ret[dataOffset:dataOffset+4], uint32(now))
|
||||
// rand size+10~(rand size+14), client ID
|
||||
copy(ret[dataOffset+4:dataOffset+4+4], a.clientID[:4])
|
||||
// rand size+14~(rand size+18), connection ID
|
||||
binary.LittleEndian.PutUint32(ret[dataOffset+8:dataOffset+8+4], a.connectionID)
|
||||
// rand size+18~(rand size+18)+data length, data
|
||||
copy(ret[dataOffset+12:], data)
|
||||
|
||||
key := make([]byte, len(a.IV)+len(a.Key))
|
||||
copy(key, a.IV)
|
||||
copy(key[len(a.IV):], a.Key)
|
||||
|
||||
h := tools.HmacSHA1(key, ret[:retSize-tools.HmacSHA1Len])
|
||||
// (ret size-10)~(ret size)/(rand size)+18+data length~end, hmac
|
||||
copy(ret[retSize-tools.HmacSHA1Len:], h[:tools.HmacSHA1Len])
|
||||
return ret
|
||||
}
|
||||
|
||||
func getHeadSize(data []byte, defaultValue int) int {
|
||||
if data == nil || len(data) < 2 {
|
||||
return defaultValue
|
||||
}
|
||||
headType := data[0] & 0x07
|
||||
switch headType {
|
||||
case 1:
|
||||
// IPv4 1+4+2
|
||||
return 7
|
||||
case 4:
|
||||
// IPv6 1+16+2
|
||||
return 19
|
||||
case 3:
|
||||
// domain name, variant length
|
||||
return 4 + int(data[1])
|
||||
}
|
||||
|
||||
return defaultValue
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
package protocol
|
||||
|
||||
// Base information for protocol
|
||||
type Base struct {
|
||||
IV []byte
|
||||
Key []byte
|
||||
TCPMss int
|
||||
Overhead int
|
||||
Param string
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
package protocol
|
||||
|
||||
type origin struct{ *Base }
|
||||
|
||||
func init() {
|
||||
register("origin", newOrigin)
|
||||
}
|
||||
|
||||
func newOrigin(b *Base) Protocol {
|
||||
return &origin{}
|
||||
}
|
||||
|
||||
func (o *origin) initForConn(iv []byte) Protocol { return &origin{} }
|
||||
|
||||
func (o *origin) GetProtocolOverhead() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (o *origin) SetOverhead(overhead int) {
|
||||
}
|
||||
|
||||
func (o *origin) Decode(b []byte) ([]byte, int, error) {
|
||||
return b, len(b), nil
|
||||
}
|
||||
|
||||
func (o *origin) Encode(b []byte) ([]byte, error) {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (o *origin) DecodePacket(b []byte) ([]byte, int, error) {
|
||||
return b, len(b), nil
|
||||
}
|
||||
|
||||
func (o *origin) EncodePacket(b []byte) ([]byte, error) {
|
||||
return b, nil
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
)
|
||||
|
||||
// NewPacketConn returns a net.NewPacketConn with protocol decoding/encoding
|
||||
func NewPacketConn(pc net.PacketConn, p Protocol) net.PacketConn {
|
||||
return &PacketConn{PacketConn: pc, Protocol: p.initForConn(nil)}
|
||||
}
|
||||
|
||||
// PacketConn represents a protocol packet connection
|
||||
type PacketConn struct {
|
||||
net.PacketConn
|
||||
Protocol
|
||||
}
|
||||
|
||||
func (c *PacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||
buf := pool.Get(pool.RelayBufferSize)
|
||||
defer pool.Put(buf)
|
||||
buf, err := c.EncodePacket(b)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
_, err = c.PacketConn.WriteTo(buf, addr)
|
||||
return len(b), err
|
||||
}
|
||||
|
||||
func (c *PacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||
n, addr, err := c.PacketConn.ReadFrom(b)
|
||||
if err != nil {
|
||||
return n, addr, err
|
||||
}
|
||||
bb, length, err := c.DecodePacket(b[:n])
|
||||
if err != nil {
|
||||
return n, addr, err
|
||||
}
|
||||
copy(b, bb)
|
||||
return length, addr, err
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
errAuthAES128IncorrectMAC = errors.New("auth_aes128_* post decrypt incorrect mac")
|
||||
errAuthAES128DataLengthError = errors.New("auth_aes128_* post decrypt length mismatch")
|
||||
errAuthAES128IncorrectChecksum = errors.New("auth_aes128_* post decrypt incorrect checksum")
|
||||
errAuthAES128PositionTooLarge = errors.New("auth_aes128_* post decrypt position is too large")
|
||||
errAuthSHA1v4CRC32Error = errors.New("auth_sha1_v4 post decrypt data crc32 error")
|
||||
errAuthSHA1v4DataLengthError = errors.New("auth_sha1_v4 post decrypt data length error")
|
||||
errAuthSHA1v4IncorrectChecksum = errors.New("auth_sha1_v4 post decrypt incorrect checksum")
|
||||
errAuthChainDataLengthError = errors.New("auth_chain_* post decrypt length mismatch")
|
||||
errAuthChainHMACError = errors.New("auth_chain_* post decrypt hmac error")
|
||||
)
|
||||
|
||||
type authData struct {
|
||||
clientID []byte
|
||||
connectionID uint32
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
type recvInfo struct {
|
||||
recvID uint32
|
||||
buffer *bytes.Buffer
|
||||
}
|
||||
|
||||
type hmacMethod func(key []byte, data []byte) []byte
|
||||
type hashDigestMethod func(data []byte) []byte
|
||||
type rndMethod func(dataSize int, random *shift128PlusContext, lastHash []byte, dataSizeList, dataSizeList2 []int, overhead int) int
|
||||
|
||||
// Protocol provides methods for decoding, encoding and iv setting
|
||||
type Protocol interface {
|
||||
initForConn(iv []byte) Protocol
|
||||
GetProtocolOverhead() int
|
||||
SetOverhead(int)
|
||||
Decode([]byte) ([]byte, int, error)
|
||||
Encode([]byte) ([]byte, error)
|
||||
DecodePacket([]byte) ([]byte, int, error)
|
||||
EncodePacket([]byte) ([]byte, error)
|
||||
}
|
||||
|
||||
type protocolCreator func(b *Base) Protocol
|
||||
|
||||
var protocolList = make(map[string]protocolCreator)
|
||||
|
||||
func register(name string, c protocolCreator) {
|
||||
protocolList[name] = c
|
||||
}
|
||||
|
||||
// PickProtocol returns a protocol of the given name
|
||||
func PickProtocol(name string, b *Base) (Protocol, error) {
|
||||
if protocolCreator, ok := protocolList[strings.ToLower(name)]; ok {
|
||||
return protocolCreator(b), nil
|
||||
}
|
||||
return nil, fmt.Errorf("Protocol %s not supported", name)
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
)
|
||||
|
||||
// NewConn wraps a stream-oriented net.Conn with protocol decoding/encoding
|
||||
func NewConn(c net.Conn, p Protocol, iv []byte) net.Conn {
|
||||
return &Conn{Conn: c, Protocol: p.initForConn(iv)}
|
||||
}
|
||||
|
||||
// Conn represents a protocol connection
|
||||
type Conn struct {
|
||||
net.Conn
|
||||
Protocol
|
||||
buf []byte
|
||||
offset int
|
||||
underDecoded bytes.Buffer
|
||||
}
|
||||
|
||||
func (c *Conn) Read(b []byte) (int, error) {
|
||||
if c.buf != nil {
|
||||
n := copy(b, c.buf[c.offset:])
|
||||
c.offset += n
|
||||
if c.offset == len(c.buf) {
|
||||
c.buf = nil
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
buf := pool.Get(pool.RelayBufferSize)
|
||||
defer pool.Put(buf)
|
||||
n, err := c.Conn.Read(buf)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
c.underDecoded.Write(buf[:n])
|
||||
underDecoded := c.underDecoded.Bytes()
|
||||
decoded, length, err := c.Decode(underDecoded)
|
||||
if err != nil {
|
||||
c.underDecoded.Reset()
|
||||
return 0, nil
|
||||
}
|
||||
if length == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
c.underDecoded.Next(length)
|
||||
n = copy(b, decoded)
|
||||
if len(decoded) > len(b) {
|
||||
c.buf = decoded
|
||||
c.offset = n
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (c *Conn) Write(b []byte) (int, error) {
|
||||
encoded, err := c.Encode(b)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
_, err = c.Conn.Write(encoded)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
@ -23,7 +23,7 @@ type DomainTrie struct {
|
||||
root *Node
|
||||
}
|
||||
|
||||
func validAndSplitDomain(domain string) ([]string, bool) {
|
||||
func ValidAndSplitDomain(domain string) ([]string, bool) {
|
||||
if domain != "" && domain[len(domain)-1] == '.' {
|
||||
return nil, false
|
||||
}
|
||||
@ -54,7 +54,7 @@ func validAndSplitDomain(domain string) ([]string, bool) {
|
||||
// 4. .example.com
|
||||
// 5. +.example.com
|
||||
func (t *DomainTrie) Insert(domain string, data interface{}) error {
|
||||
parts, valid := validAndSplitDomain(domain)
|
||||
parts, valid := ValidAndSplitDomain(domain)
|
||||
if !valid {
|
||||
return ErrInvalidDomain
|
||||
}
|
||||
@ -91,7 +91,7 @@ func (t *DomainTrie) insert(parts []string, data interface{}) {
|
||||
// 2. wildcard domain
|
||||
// 2. dot wildcard domain
|
||||
func (t *DomainTrie) Search(domain string) *Node {
|
||||
parts, valid := validAndSplitDomain(domain)
|
||||
parts, valid := ValidAndSplitDomain(domain)
|
||||
if !valid || parts[0] == "" {
|
||||
return nil
|
||||
}
|
||||
@ -122,11 +122,7 @@ func (t *DomainTrie) search(node *Node, parts []string) *Node {
|
||||
}
|
||||
}
|
||||
|
||||
if c := node.getChild(dotWildcard); c != nil {
|
||||
return c
|
||||
}
|
||||
|
||||
return nil
|
||||
return node.getChild(dotWildcard)
|
||||
}
|
||||
|
||||
// New returns a new, empty Trie.
|
||||
|
@ -8,16 +8,17 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/Dreamacro/clash/adapters/outbound"
|
||||
"github.com/Dreamacro/clash/adapters/outboundgroup"
|
||||
"github.com/Dreamacro/clash/adapters/provider"
|
||||
"github.com/Dreamacro/clash/adapter"
|
||||
"github.com/Dreamacro/clash/adapter/outbound"
|
||||
"github.com/Dreamacro/clash/adapter/outboundgroup"
|
||||
"github.com/Dreamacro/clash/adapter/provider"
|
||||
"github.com/Dreamacro/clash/component/auth"
|
||||
"github.com/Dreamacro/clash/component/fakeip"
|
||||
"github.com/Dreamacro/clash/component/trie"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/dns"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
R "github.com/Dreamacro/clash/rules"
|
||||
R "github.com/Dreamacro/clash/rule"
|
||||
T "github.com/Dreamacro/clash/tunnel"
|
||||
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
@ -30,7 +31,7 @@ type General struct {
|
||||
Mode T.TunnelMode `json:"mode"`
|
||||
LogLevel log.LogLevel `json:"log-level"`
|
||||
IPv6 bool `json:"ipv6"`
|
||||
Interface string `json:"interface-name"`
|
||||
Interface string `json:"-"`
|
||||
}
|
||||
|
||||
// Inbound
|
||||
@ -64,6 +65,7 @@ type DNS struct {
|
||||
DefaultNameserver []dns.NameServer `yaml:"default-nameserver"`
|
||||
FakeIPRange *fakeip.Pool
|
||||
Hosts *trie.DomainTrie
|
||||
NameServerPolicy map[string]dns.NameServer
|
||||
}
|
||||
|
||||
// FallbackFilter config
|
||||
@ -73,6 +75,11 @@ type FallbackFilter struct {
|
||||
Domain []string `yaml:"domain"`
|
||||
}
|
||||
|
||||
// Profile config
|
||||
type Profile struct {
|
||||
StoreSelected bool `yaml:"store-selected"`
|
||||
}
|
||||
|
||||
// Experimental config
|
||||
type Experimental struct{}
|
||||
|
||||
@ -82,6 +89,7 @@ type Config struct {
|
||||
DNS *DNS
|
||||
Experimental *Experimental
|
||||
Hosts *trie.DomainTrie
|
||||
Profile *Profile
|
||||
Rules []C.Rule
|
||||
Users []auth.AuthUser
|
||||
Proxies map[string]C.Proxy
|
||||
@ -100,6 +108,7 @@ type RawDNS struct {
|
||||
FakeIPRange string `yaml:"fake-ip-range"`
|
||||
FakeIPFilter []string `yaml:"fake-ip-filter"`
|
||||
DefaultNameserver []string `yaml:"default-nameserver"`
|
||||
NameServerPolicy map[string]string `yaml:"nameserver-policy"`
|
||||
}
|
||||
|
||||
type RawFallbackFilter struct {
|
||||
@ -129,6 +138,7 @@ type RawConfig struct {
|
||||
Hosts map[string]string `yaml:"hosts"`
|
||||
DNS RawDNS `yaml:"dns"`
|
||||
Experimental Experimental `yaml:"experimental"`
|
||||
Profile Profile `yaml:"profile"`
|
||||
Proxy []map[string]interface{} `yaml:"proxies"`
|
||||
ProxyGroup []map[string]interface{} `yaml:"proxy-groups"`
|
||||
Rule []string `yaml:"rules"`
|
||||
@ -145,7 +155,7 @@ func Parse(buf []byte) (*Config, error) {
|
||||
}
|
||||
|
||||
func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
||||
// config with some default value
|
||||
// config with default value
|
||||
rawCfg := &RawConfig{
|
||||
AllowLan: false,
|
||||
BindAddress: "*",
|
||||
@ -169,6 +179,9 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
||||
"8.8.8.8",
|
||||
},
|
||||
},
|
||||
Profile: Profile{
|
||||
StoreSelected: true,
|
||||
},
|
||||
}
|
||||
|
||||
if err := yaml.Unmarshal(buf, &rawCfg); err != nil {
|
||||
@ -182,6 +195,7 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
||||
config := &Config{}
|
||||
|
||||
config.Experimental = &rawCfg.Experimental
|
||||
config.Profile = &rawCfg.Profile
|
||||
|
||||
general, err := parseGeneral(rawCfg)
|
||||
if err != nil {
|
||||
@ -261,13 +275,13 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
||||
groupsConfig := cfg.ProxyGroup
|
||||
providersConfig := cfg.ProxyProvider
|
||||
|
||||
proxies["DIRECT"] = outbound.NewProxy(outbound.NewDirect())
|
||||
proxies["REJECT"] = outbound.NewProxy(outbound.NewReject())
|
||||
proxies["DIRECT"] = adapter.NewProxy(outbound.NewDirect())
|
||||
proxies["REJECT"] = adapter.NewProxy(outbound.NewReject())
|
||||
proxyList = append(proxyList, "DIRECT", "REJECT")
|
||||
|
||||
// parse proxy
|
||||
for idx, mapping := range proxiesConfig {
|
||||
proxy, err := outbound.ParseProxy(mapping)
|
||||
proxy, err := adapter.ParseProxy(mapping)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("proxy %d: %w", idx, err)
|
||||
}
|
||||
@ -326,7 +340,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
||||
return nil, nil, fmt.Errorf("proxy group %s: the duplicate name", groupName)
|
||||
}
|
||||
|
||||
proxies[groupName] = outbound.NewProxy(group)
|
||||
proxies[groupName] = adapter.NewProxy(group)
|
||||
}
|
||||
|
||||
// initial compatible provider
|
||||
@ -355,7 +369,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
||||
},
|
||||
[]provider.ProxyProvider{pd},
|
||||
)
|
||||
proxies["GLOBAL"] = outbound.NewProxy(global)
|
||||
proxies["GLOBAL"] = adapter.NewProxy(global)
|
||||
return proxies, providersMap, nil
|
||||
}
|
||||
|
||||
@ -489,6 +503,23 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) {
|
||||
return nameservers, nil
|
||||
}
|
||||
|
||||
func parseNameServerPolicy(nsPolicy map[string]string) (map[string]dns.NameServer, error) {
|
||||
policy := map[string]dns.NameServer{}
|
||||
|
||||
for domain, server := range nsPolicy {
|
||||
nameservers, err := parseNameServer([]string{server})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, valid := trie.ValidAndSplitDomain(domain); !valid {
|
||||
return nil, fmt.Errorf("DNS ResoverRule invalid domain: %s", domain)
|
||||
}
|
||||
policy[domain] = nameservers[0]
|
||||
}
|
||||
|
||||
return policy, nil
|
||||
}
|
||||
|
||||
func parseFallbackIPCIDR(ips []string) ([]*net.IPNet, error) {
|
||||
ipNets := []*net.IPNet{}
|
||||
|
||||
@ -526,6 +557,10 @@ func parseDNS(cfg RawDNS, hosts *trie.DomainTrie) (*DNS, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if dnsCfg.NameServerPolicy, err = parseNameServerPolicy(cfg.NameServerPolicy); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(cfg.DefaultNameserver) == 0 {
|
||||
return nil, errors.New("default nameserver should have at least one nameserver")
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
func downloadMMDB(path string) (err error) {
|
||||
resp, err := http.Get("https://github.com/Dreamacro/maxmind-geoip/releases/latest/download/Country.mmdb")
|
||||
resp, err := http.Get("https://cdn.jsdelivr.net/gh/Dreamacro/maxmind-geoip@release/Country.mmdb")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -66,7 +66,7 @@ func Init(dir string) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't create file %s: %s", C.Path.Config(), err.Error())
|
||||
}
|
||||
f.Write([]byte(`port: 7890`))
|
||||
f.Write([]byte(`mixed-port: 7890`))
|
||||
f.Close()
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/Dreamacro/clash/adapters/outboundgroup"
|
||||
"github.com/Dreamacro/clash/adapter/outboundgroup"
|
||||
"github.com/Dreamacro/clash/common/structure"
|
||||
)
|
||||
|
||||
|
@ -27,10 +27,9 @@ const (
|
||||
LoadBalance
|
||||
)
|
||||
|
||||
type ServerAdapter interface {
|
||||
net.Conn
|
||||
Metadata() *Metadata
|
||||
}
|
||||
const (
|
||||
DefaultTCPTimeout = 5 * time.Second
|
||||
)
|
||||
|
||||
type Connection interface {
|
||||
Chains() Chain
|
||||
@ -50,6 +49,15 @@ func (c Chain) String() string {
|
||||
}
|
||||
}
|
||||
|
||||
func (c Chain) Last() string {
|
||||
switch len(c) {
|
||||
case 0:
|
||||
return ""
|
||||
default:
|
||||
return c[0]
|
||||
}
|
||||
}
|
||||
|
||||
type Conn interface {
|
||||
net.Conn
|
||||
Connection
|
||||
@ -65,8 +73,21 @@ type PacketConn interface {
|
||||
type ProxyAdapter interface {
|
||||
Name() string
|
||||
Type() AdapterType
|
||||
|
||||
// StreamConn wraps a protocol around net.Conn with Metadata.
|
||||
//
|
||||
// Examples:
|
||||
// conn, _ := net.Dial("tcp", "host:port")
|
||||
// conn, _ = adapter.StreamConn(conn, metadata)
|
||||
//
|
||||
// It returns a C.Conn with protocol which start with
|
||||
// a new session (if any)
|
||||
StreamConn(c net.Conn, metadata *Metadata) (net.Conn, error)
|
||||
|
||||
// DialContext return a C.Conn with protocol which
|
||||
// contains multiplexing-related reuse logic (if any)
|
||||
DialContext(ctx context.Context, metadata *Metadata) (Conn, error)
|
||||
|
||||
DialUDP(metadata *Metadata) (PacketConn, error)
|
||||
SupportUDP() bool
|
||||
MarshalJSON() ([]byte, error)
|
||||
|
23
constant/context.go
Normal file
23
constant/context.go
Normal file
@ -0,0 +1,23 @@
|
||||
package constant
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
)
|
||||
|
||||
type PlainContext interface {
|
||||
ID() uuid.UUID
|
||||
}
|
||||
|
||||
type ConnContext interface {
|
||||
PlainContext
|
||||
Metadata() *Metadata
|
||||
Conn() net.Conn
|
||||
}
|
||||
|
||||
type PacketConnContext interface {
|
||||
PlainContext
|
||||
Metadata() *Metadata
|
||||
PacketConn() net.PacketConn
|
||||
}
|
@ -56,3 +56,7 @@ func (p *path) Resolve(path string) string {
|
||||
func (p *path) MMDB() string {
|
||||
return P.Join(p.homeDir, "Country.mmdb")
|
||||
}
|
||||
|
||||
func (p *path) Cache() string {
|
||||
return P.Join(p.homeDir, ".cache")
|
||||
}
|
||||
|
39
context/conn.go
Normal file
39
context/conn.go
Normal file
@ -0,0 +1,39 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
)
|
||||
|
||||
type ConnContext struct {
|
||||
id uuid.UUID
|
||||
metadata *C.Metadata
|
||||
conn net.Conn
|
||||
}
|
||||
|
||||
func NewConnContext(conn net.Conn, metadata *C.Metadata) *ConnContext {
|
||||
id, _ := uuid.NewV4()
|
||||
return &ConnContext{
|
||||
id: id,
|
||||
metadata: metadata,
|
||||
conn: conn,
|
||||
}
|
||||
}
|
||||
|
||||
// ID implement C.ConnContext ID
|
||||
func (c *ConnContext) ID() uuid.UUID {
|
||||
return c.id
|
||||
}
|
||||
|
||||
// Metadata implement C.ConnContext Metadata
|
||||
func (c *ConnContext) Metadata() *C.Metadata {
|
||||
return c.metadata
|
||||
}
|
||||
|
||||
// Conn implement C.ConnContext Conn
|
||||
func (c *ConnContext) Conn() net.Conn {
|
||||
return c.conn
|
||||
}
|
41
context/dns.go
Normal file
41
context/dns.go
Normal file
@ -0,0 +1,41 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
const (
|
||||
DNSTypeHost = "host"
|
||||
DNSTypeFakeIP = "fakeip"
|
||||
DNSTypeRaw = "raw"
|
||||
)
|
||||
|
||||
type DNSContext struct {
|
||||
id uuid.UUID
|
||||
msg *dns.Msg
|
||||
tp string
|
||||
}
|
||||
|
||||
func NewDNSContext(msg *dns.Msg) *DNSContext {
|
||||
id, _ := uuid.NewV4()
|
||||
return &DNSContext{
|
||||
id: id,
|
||||
msg: msg,
|
||||
}
|
||||
}
|
||||
|
||||
// ID implement C.PlainContext ID
|
||||
func (c *DNSContext) ID() uuid.UUID {
|
||||
return c.id
|
||||
}
|
||||
|
||||
// SetType set type of response
|
||||
func (c *DNSContext) SetType(tp string) {
|
||||
c.tp = tp
|
||||
}
|
||||
|
||||
// Type return type of response
|
||||
func (c *DNSContext) Type() string {
|
||||
return c.tp
|
||||
}
|
43
context/packetconn.go
Normal file
43
context/packetconn.go
Normal file
@ -0,0 +1,43 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
)
|
||||
|
||||
type PacketConnContext struct {
|
||||
id uuid.UUID
|
||||
metadata *C.Metadata
|
||||
packetConn net.PacketConn
|
||||
}
|
||||
|
||||
func NewPacketConnContext(metadata *C.Metadata) *PacketConnContext {
|
||||
id, _ := uuid.NewV4()
|
||||
return &PacketConnContext{
|
||||
id: id,
|
||||
metadata: metadata,
|
||||
}
|
||||
}
|
||||
|
||||
// ID implement C.PacketConnContext ID
|
||||
func (pc *PacketConnContext) ID() uuid.UUID {
|
||||
return pc.id
|
||||
}
|
||||
|
||||
// Metadata implement C.PacketConnContext Metadata
|
||||
func (pc *PacketConnContext) Metadata() *C.Metadata {
|
||||
return pc.metadata
|
||||
}
|
||||
|
||||
// PacketConn implement C.PacketConnContext PacketConn
|
||||
func (pc *PacketConnContext) PacketConn() net.PacketConn {
|
||||
return pc.packetConn
|
||||
}
|
||||
|
||||
// InjectPacketConn injectPacketConn manually
|
||||
func (pc *PacketConnContext) InjectPacketConn(pconn C.PacketConn) {
|
||||
pc.packetConn = pconn
|
||||
}
|
@ -7,6 +7,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
|
||||
D "github.com/miekg/dns"
|
||||
)
|
||||
@ -28,8 +29,7 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err
|
||||
// a default ip dns
|
||||
ip = net.ParseIP(c.host)
|
||||
} else {
|
||||
var err error
|
||||
if ip, err = c.r.ResolveIP(c.host); err != nil {
|
||||
if ip, err = resolver.ResolveIPWithResolver(c.host, c.r); err != nil {
|
||||
return nil, fmt.Errorf("use default dns resolve failed: %w", err)
|
||||
}
|
||||
}
|
||||
@ -39,7 +39,7 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if dialer.DialHook != nil {
|
||||
if ip != nil && ip.IsGlobalUnicast() && dialer.DialHook != nil {
|
||||
network := "udp"
|
||||
if strings.HasPrefix(c.Client.Net, "tcp") {
|
||||
network = "tcp"
|
||||
|
@ -3,12 +3,12 @@ package dns
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
|
||||
D "github.com/miekg/dns"
|
||||
)
|
||||
@ -75,7 +75,6 @@ func newDoHClient(url string, r *Resolver) *dohClient {
|
||||
return &dohClient{
|
||||
url: url,
|
||||
transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{ClientSessionCache: globalSessionCache},
|
||||
ForceAttemptHTTP2: true,
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
host, port, err := net.SplitHostPort(addr)
|
||||
@ -83,12 +82,12 @@ func newDoHClient(url string, r *Resolver) *dohClient {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ip, err := r.ResolveIPv4(host)
|
||||
ip, err := resolver.ResolveIPWithResolver(host, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dialer.DialContext(ctx, "tcp4", net.JoinHostPort(ip.String(), port))
|
||||
return dialer.DialContext(ctx, "tcp", net.JoinHostPort(ip.String(), port))
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -8,26 +8,27 @@ import (
|
||||
"github.com/Dreamacro/clash/common/cache"
|
||||
"github.com/Dreamacro/clash/component/fakeip"
|
||||
"github.com/Dreamacro/clash/component/trie"
|
||||
"github.com/Dreamacro/clash/context"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
D "github.com/miekg/dns"
|
||||
)
|
||||
|
||||
type handler func(r *D.Msg) (*D.Msg, error)
|
||||
type handler func(ctx *context.DNSContext, r *D.Msg) (*D.Msg, error)
|
||||
type middleware func(next handler) handler
|
||||
|
||||
func withHosts(hosts *trie.DomainTrie) middleware {
|
||||
return func(next handler) handler {
|
||||
return func(r *D.Msg) (*D.Msg, error) {
|
||||
return func(ctx *context.DNSContext, r *D.Msg) (*D.Msg, error) {
|
||||
q := r.Question[0]
|
||||
|
||||
if !isIPRequest(q) {
|
||||
return next(r)
|
||||
return next(ctx, r)
|
||||
}
|
||||
|
||||
record := hosts.Search(strings.TrimRight(q.Name, "."))
|
||||
if record == nil {
|
||||
return next(r)
|
||||
return next(ctx, r)
|
||||
}
|
||||
|
||||
ip := record.Data.(net.IP)
|
||||
@ -46,9 +47,10 @@ func withHosts(hosts *trie.DomainTrie) middleware {
|
||||
|
||||
msg.Answer = []D.RR{rr}
|
||||
} else {
|
||||
return next(r)
|
||||
return next(ctx, r)
|
||||
}
|
||||
|
||||
ctx.SetType(context.DNSTypeHost)
|
||||
msg.SetRcode(r, D.RcodeSuccess)
|
||||
msg.Authoritative = true
|
||||
msg.RecursionAvailable = true
|
||||
@ -60,14 +62,14 @@ func withHosts(hosts *trie.DomainTrie) middleware {
|
||||
|
||||
func withMapping(mapping *cache.LruCache) middleware {
|
||||
return func(next handler) handler {
|
||||
return func(r *D.Msg) (*D.Msg, error) {
|
||||
return func(ctx *context.DNSContext, r *D.Msg) (*D.Msg, error) {
|
||||
q := r.Question[0]
|
||||
|
||||
if !isIPRequest(q) {
|
||||
return next(r)
|
||||
return next(ctx, r)
|
||||
}
|
||||
|
||||
msg, err := next(r)
|
||||
msg, err := next(ctx, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -99,12 +101,12 @@ func withMapping(mapping *cache.LruCache) middleware {
|
||||
|
||||
func withFakeIP(fakePool *fakeip.Pool) middleware {
|
||||
return func(next handler) handler {
|
||||
return func(r *D.Msg) (*D.Msg, error) {
|
||||
return func(ctx *context.DNSContext, r *D.Msg) (*D.Msg, error) {
|
||||
q := r.Question[0]
|
||||
|
||||
host := strings.TrimRight(q.Name, ".")
|
||||
if fakePool.LookupHost(host) {
|
||||
return next(r)
|
||||
return next(ctx, r)
|
||||
}
|
||||
|
||||
switch q.Qtype {
|
||||
@ -113,7 +115,7 @@ func withFakeIP(fakePool *fakeip.Pool) middleware {
|
||||
}
|
||||
|
||||
if q.Qtype != D.TypeA {
|
||||
return next(r)
|
||||
return next(ctx, r)
|
||||
}
|
||||
|
||||
rr := &D.A{}
|
||||
@ -123,6 +125,7 @@ func withFakeIP(fakePool *fakeip.Pool) middleware {
|
||||
msg := r.Copy()
|
||||
msg.Answer = []D.RR{rr}
|
||||
|
||||
ctx.SetType(context.DNSTypeFakeIP)
|
||||
setMsgTTL(msg, 1)
|
||||
msg.SetRcode(r, D.RcodeSuccess)
|
||||
msg.Authoritative = true
|
||||
@ -134,7 +137,8 @@ func withFakeIP(fakePool *fakeip.Pool) middleware {
|
||||
}
|
||||
|
||||
func withResolver(resolver *Resolver) handler {
|
||||
return func(r *D.Msg) (*D.Msg, error) {
|
||||
return func(ctx *context.DNSContext, r *D.Msg) (*D.Msg, error) {
|
||||
ctx.SetType(context.DNSTypeRaw)
|
||||
q := r.Question[0]
|
||||
|
||||
// return a empty AAAA msg when ipv6 disabled
|
||||
|
@ -2,7 +2,6 @@ package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
@ -20,10 +19,6 @@ import (
|
||||
"golang.org/x/sync/singleflight"
|
||||
)
|
||||
|
||||
var (
|
||||
globalSessionCache = tls.NewLRUClientSessionCache(64)
|
||||
)
|
||||
|
||||
type dnsClient interface {
|
||||
Exchange(m *D.Msg) (msg *D.Msg, err error)
|
||||
ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error)
|
||||
@ -43,6 +38,7 @@ type Resolver struct {
|
||||
fallbackIPFilters []fallbackIPFilter
|
||||
group singleflight.Group
|
||||
lruCache *cache.LruCache
|
||||
policy *trie.DomainTrie
|
||||
}
|
||||
|
||||
// ResolveIP request with TypeA and TypeAAAA, priority return TypeA
|
||||
@ -131,6 +127,9 @@ func (r *Resolver) exchangeWithoutCache(m *D.Msg) (msg *D.Msg, err error) {
|
||||
return r.ipExchange(m)
|
||||
}
|
||||
|
||||
if matched := r.matchPolicy(m); len(matched) != 0 {
|
||||
return r.batchExchange(matched, m)
|
||||
}
|
||||
return r.batchExchange(r.main, m)
|
||||
})
|
||||
|
||||
@ -145,7 +144,7 @@ func (r *Resolver) exchangeWithoutCache(m *D.Msg) (msg *D.Msg, err error) {
|
||||
}
|
||||
|
||||
func (r *Resolver) batchExchange(clients []dnsClient, m *D.Msg) (msg *D.Msg, err error) {
|
||||
fast, ctx := picker.WithTimeout(context.Background(), time.Second*5)
|
||||
fast, ctx := picker.WithTimeout(context.Background(), resolver.DefaultDNSTimeout)
|
||||
for _, client := range clients {
|
||||
r := client
|
||||
fast.Go(func() (interface{}, error) {
|
||||
@ -172,6 +171,24 @@ func (r *Resolver) batchExchange(clients []dnsClient, m *D.Msg) (msg *D.Msg, err
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Resolver) matchPolicy(m *D.Msg) []dnsClient {
|
||||
if r.policy == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
domain := r.msgToDomain(m)
|
||||
if domain == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
record := r.policy.Search(domain)
|
||||
if record == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return record.Data.([]dnsClient)
|
||||
}
|
||||
|
||||
func (r *Resolver) shouldOnlyQueryFallback(m *D.Msg) bool {
|
||||
if r.fallback == nil || len(r.fallbackDomainFilters) == 0 {
|
||||
return false
|
||||
@ -194,6 +211,11 @@ func (r *Resolver) shouldOnlyQueryFallback(m *D.Msg) bool {
|
||||
|
||||
func (r *Resolver) ipExchange(m *D.Msg) (msg *D.Msg, err error) {
|
||||
|
||||
if matched := r.matchPolicy(m); len(matched) != 0 {
|
||||
res := <-r.asyncExchange(matched, m)
|
||||
return res.Msg, res.Error
|
||||
}
|
||||
|
||||
onlyFallback := r.shouldOnlyQueryFallback(m)
|
||||
|
||||
if onlyFallback {
|
||||
@ -212,7 +234,7 @@ func (r *Resolver) ipExchange(m *D.Msg) (msg *D.Msg, err error) {
|
||||
fallbackMsg := r.asyncExchange(r.fallback, m)
|
||||
res := <-msgCh
|
||||
if res.Error == nil {
|
||||
if ips := r.msgToIP(res.Msg); len(ips) != 0 {
|
||||
if ips := msgToIP(res.Msg); len(ips) != 0 {
|
||||
if !r.shouldIPFallback(ips[0]) {
|
||||
msg = res.Msg // no need to wait for fallback result
|
||||
err = res.Error
|
||||
@ -247,7 +269,7 @@ func (r *Resolver) resolveIP(host string, dnsType uint16) (ip net.IP, err error)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ips := r.msgToIP(msg)
|
||||
ips := msgToIP(msg)
|
||||
ipLength := len(ips)
|
||||
if ipLength == 0 {
|
||||
return nil, resolver.ErrIPNotFound
|
||||
@ -257,21 +279,6 @@ func (r *Resolver) resolveIP(host string, dnsType uint16) (ip net.IP, err error)
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Resolver) msgToIP(msg *D.Msg) []net.IP {
|
||||
ips := []net.IP{}
|
||||
|
||||
for _, answer := range msg.Answer {
|
||||
switch ans := answer.(type) {
|
||||
case *D.AAAA:
|
||||
ips = append(ips, ans.AAAA)
|
||||
case *D.A:
|
||||
ips = append(ips, ans.A)
|
||||
}
|
||||
}
|
||||
|
||||
return ips
|
||||
}
|
||||
|
||||
func (r *Resolver) msgToDomain(msg *D.Msg) string {
|
||||
if len(msg.Question) > 0 {
|
||||
return strings.TrimRight(msg.Question[0].Name, ".")
|
||||
@ -308,6 +315,7 @@ type Config struct {
|
||||
FallbackFilter FallbackFilter
|
||||
Pool *fakeip.Pool
|
||||
Hosts *trie.DomainTrie
|
||||
Policy map[string]NameServer
|
||||
}
|
||||
|
||||
func NewResolver(config Config) *Resolver {
|
||||
@ -327,6 +335,13 @@ func NewResolver(config Config) *Resolver {
|
||||
r.fallback = transform(config.Fallback, defaultResolver)
|
||||
}
|
||||
|
||||
if len(config.Policy) != 0 {
|
||||
r.policy = trie.New()
|
||||
for domain, nameserver := range config.Policy {
|
||||
r.policy.Insert(domain, transform([]NameServer{nameserver}, defaultResolver))
|
||||
}
|
||||
}
|
||||
|
||||
fallbackIPFilters := []fallbackIPFilter{}
|
||||
if config.FallbackFilter.GeoIP {
|
||||
fallbackIPFilters = append(fallbackIPFilters, &geoipFilter{})
|
||||
|
@ -1,9 +1,11 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/common/sockopt"
|
||||
"github.com/Dreamacro/clash/context"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
D "github.com/miekg/dns"
|
||||
@ -21,21 +23,26 @@ type Server struct {
|
||||
handler handler
|
||||
}
|
||||
|
||||
// ServeDNS implement D.Handler ServeDNS
|
||||
func (s *Server) ServeDNS(w D.ResponseWriter, r *D.Msg) {
|
||||
if len(r.Question) == 0 {
|
||||
D.HandleFailed(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
msg, err := s.handler(r)
|
||||
msg, err := handlerWithContext(s.handler, r)
|
||||
if err != nil {
|
||||
D.HandleFailed(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
msg.Compress = true
|
||||
w.WriteMsg(msg)
|
||||
}
|
||||
|
||||
func handlerWithContext(handler handler, msg *D.Msg) (*D.Msg, error) {
|
||||
if len(msg.Question) == 0 {
|
||||
return nil, errors.New("at least one question is required")
|
||||
}
|
||||
|
||||
ctx := context.NewDNSContext(msg)
|
||||
return handler(ctx, msg)
|
||||
}
|
||||
|
||||
func (s *Server) setHandler(handler handler) {
|
||||
s.handler = handler
|
||||
}
|
||||
|
16
dns/util.go
16
dns/util.go
@ -127,7 +127,6 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient {
|
||||
Client: &D.Client{
|
||||
Net: s.Net,
|
||||
TLSConfig: &tls.Config{
|
||||
ClientSessionCache: globalSessionCache,
|
||||
// alpn identifier, see https://tools.ietf.org/html/draft-hoffman-dprive-dns-tls-alpn-00#page-6
|
||||
NextProtos: []string{"dns"},
|
||||
ServerName: host,
|
||||
@ -153,3 +152,18 @@ func handleMsgWithEmptyAnswer(r *D.Msg) *D.Msg {
|
||||
|
||||
return msg
|
||||
}
|
||||
|
||||
func msgToIP(msg *D.Msg) []net.IP {
|
||||
ips := []net.IP{}
|
||||
|
||||
for _, answer := range msg.Answer {
|
||||
switch ans := answer.(type) {
|
||||
case *D.AAAA:
|
||||
ips = append(ips, ans.AAAA)
|
||||
case *D.A:
|
||||
ips = append(ips, ans.A)
|
||||
}
|
||||
}
|
||||
|
||||
return ips
|
||||
}
|
||||
|
28
go.mod
28
go.mod
@ -1,22 +1,22 @@
|
||||
module github.com/Dreamacro/clash
|
||||
|
||||
go 1.15
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.6
|
||||
github.com/go-chi/chi v4.1.2+incompatible
|
||||
github.com/go-chi/cors v1.1.1
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.7
|
||||
github.com/go-chi/chi/v5 v5.0.3
|
||||
github.com/go-chi/cors v1.2.0
|
||||
github.com/go-chi/render v1.0.1
|
||||
github.com/gofrs/uuid v3.3.0+incompatible
|
||||
github.com/gofrs/uuid v4.0.0+incompatible
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
github.com/miekg/dns v1.1.35
|
||||
github.com/oschwald/geoip2-golang v1.4.0
|
||||
github.com/sirupsen/logrus v1.7.0
|
||||
github.com/stretchr/testify v1.6.1
|
||||
go.uber.org/atomic v1.7.0
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a
|
||||
golang.org/x/sys v0.0.0-20201223074533-0d417f636930
|
||||
github.com/miekg/dns v1.1.43
|
||||
github.com/oschwald/geoip2-golang v1.5.0
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/stretchr/testify v1.7.0
|
||||
go.uber.org/atomic v1.8.0
|
||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e
|
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
82
go.sum
82
go.sum
@ -1,71 +1,59 @@
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.6 h1:PysSf9sLT3Qn8jhlin5v7Rk68gOQG4K5BZFY1nxLGxI=
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.6/go.mod h1:LSXCjyHesPY3pLjhwff1mQX72ItcBT/N2xNC685cYeU=
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.7 h1:8CtbE1HoPPMfrQZGXmlluq6dO2lL31W6WRRE8fabc4Q=
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.7/go.mod h1:8p5G4cAj5ZlXwUR+Ww63gfSikr8kvw8uw3TDwLAJpUc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec=
|
||||
github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-chi/cors v1.1.1 h1:eHuqxsIw89iXcWnWUN8R72JMibABJTN/4IOYI5WERvw=
|
||||
github.com/go-chi/cors v1.1.1/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I=
|
||||
github.com/go-chi/chi/v5 v5.0.3 h1:khYQBdPivkYG1s1TAzDQG1f6eX4kD2TItYVZexL5rS4=
|
||||
github.com/go-chi/chi/v5 v5.0.3/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/cors v1.2.0 h1:tV1g1XENQ8ku4Bq3K9ub2AtgG+p16SmzeMSGTwrOKdE=
|
||||
github.com/go-chi/cors v1.2.0/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||
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.3.0+incompatible h1:8K4tyRfvU1CYPgJsveYFQMhpFd/wXNM7iK6rR7UHz84=
|
||||
github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/miekg/dns v1.1.35 h1:oTfOaDH+mZkdcgdIjH6yBajRGtIwcwcaR+rt23ZSrJs=
|
||||
github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/oschwald/geoip2-golang v1.4.0 h1:5RlrjCgRyIGDz/mBmPfnAF4h8k0IAcRv9PvrpOfz+Ug=
|
||||
github.com/oschwald/geoip2-golang v1.4.0/go.mod h1:8QwxJvRImBH+Zl6Aa6MaIcs5YdlZSTKtzmPGzQqi9ng=
|
||||
github.com/oschwald/maxminddb-golang v1.6.0 h1:KAJSjdHQ8Kv45nFIbtoLGrGWqHFajOIm7skTyz/+Dls=
|
||||
github.com/oschwald/maxminddb-golang v1.6.0/go.mod h1:DUJFucBg2cvqx42YmDa/+xHvb0elJtOm3o4aFQ/nb/w=
|
||||
github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
|
||||
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
|
||||
github.com/oschwald/geoip2-golang v1.5.0 h1:igg2yQIrrcRccB1ytFXqBfOHCjXWIoMv85lVJ1ONZzw=
|
||||
github.com/oschwald/geoip2-golang v1.5.0/go.mod h1:xdvYt5xQzB8ORWFqPnqMwZpCpgNagttWdoZLlJQzg7s=
|
||||
github.com/oschwald/maxminddb-golang v1.8.0 h1:Uh/DSnGoxsyp/KYbY1AuP0tYEwfs0sCph9p/UMXK/Hk=
|
||||
github.com/oschwald/maxminddb-golang v1.8.0/go.mod h1:RXZtst0N6+FY/3qCNmZMBApR19cdQj43/NM9VkrNAis=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
go.uber.org/atomic v1.8.0 h1:CUhrE4N1rqSE6FM9ecihEjRkLQu8cDfgDyoOs83mEY4=
|
||||
go.uber.org/atomic v1.8.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e h1:gsTQYXdTw2Gq7RBsWvlQ91b+aEQ6bXFUngBGuR8sPpI=
|
||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
|
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201223074533-0d417f636930 h1:vRgIt+nup/B/BwIS0g2oC0haq0iqbV3ZA+u6+0TlNCo=
|
||||
golang.org/x/sys v0.0.0-20201223074533-0d417f636930/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
|
@ -6,17 +6,21 @@ import (
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/Dreamacro/clash/adapters/provider"
|
||||
"github.com/Dreamacro/clash/adapter"
|
||||
"github.com/Dreamacro/clash/adapter/outboundgroup"
|
||||
"github.com/Dreamacro/clash/adapter/provider"
|
||||
"github.com/Dreamacro/clash/component/auth"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/profile"
|
||||
"github.com/Dreamacro/clash/component/profile/cachefile"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
"github.com/Dreamacro/clash/component/trie"
|
||||
"github.com/Dreamacro/clash/config"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/dns"
|
||||
P "github.com/Dreamacro/clash/listener"
|
||||
authStore "github.com/Dreamacro/clash/listener/auth"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
P "github.com/Dreamacro/clash/proxy"
|
||||
authStore "github.com/Dreamacro/clash/proxy/auth"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
)
|
||||
|
||||
@ -72,6 +76,7 @@ func ApplyConfig(cfg *config.Config, force bool) {
|
||||
updateDNS(cfg.DNS)
|
||||
updateHosts(cfg.Hosts)
|
||||
updateExperimental(cfg)
|
||||
updateProfile(cfg)
|
||||
}
|
||||
|
||||
func GetGeneral() *config.General {
|
||||
@ -94,6 +99,7 @@ func GetGeneral() *config.General {
|
||||
},
|
||||
Mode: tunnel.Mode(),
|
||||
LogLevel: log.Level(),
|
||||
IPv6: !resolver.DisableIPv6,
|
||||
}
|
||||
|
||||
return general
|
||||
@ -122,6 +128,7 @@ func updateDNS(c *config.DNS) {
|
||||
Domain: c.FallbackFilter.Domain,
|
||||
},
|
||||
Default: c.DefaultNameserver,
|
||||
Policy: c.NameServerPolicy,
|
||||
}
|
||||
|
||||
r := dns.NewResolver(cfg)
|
||||
@ -180,23 +187,26 @@ func updateGeneral(general *config.General, force bool) {
|
||||
bindAddress := general.BindAddress
|
||||
P.SetBindAddress(bindAddress)
|
||||
|
||||
if err := P.ReCreateHTTP(general.Port); err != nil {
|
||||
tcpIn := tunnel.TCPIn()
|
||||
udpIn := tunnel.UDPIn()
|
||||
|
||||
if err := P.ReCreateHTTP(general.Port, tcpIn); err != nil {
|
||||
log.Errorln("Start HTTP server error: %s", err.Error())
|
||||
}
|
||||
|
||||
if err := P.ReCreateSocks(general.SocksPort); err != nil {
|
||||
if err := P.ReCreateSocks(general.SocksPort, tcpIn, udpIn); err != nil {
|
||||
log.Errorln("Start SOCKS5 server error: %s", err.Error())
|
||||
}
|
||||
|
||||
if err := P.ReCreateRedir(general.RedirPort); err != nil {
|
||||
if err := P.ReCreateRedir(general.RedirPort, tcpIn, udpIn); err != nil {
|
||||
log.Errorln("Start Redir server error: %s", err.Error())
|
||||
}
|
||||
|
||||
if err := P.ReCreateTProxy(general.TProxyPort); err != nil {
|
||||
if err := P.ReCreateTProxy(general.TProxyPort, tcpIn, udpIn); err != nil {
|
||||
log.Errorln("Start TProxy server error: %s", err.Error())
|
||||
}
|
||||
|
||||
if err := P.ReCreateMixed(general.MixedPort); err != nil {
|
||||
if err := P.ReCreateMixed(general.MixedPort, tcpIn, udpIn); err != nil {
|
||||
log.Errorln("Start Mixed(http and socks5) server error: %s", err.Error())
|
||||
}
|
||||
}
|
||||
@ -208,3 +218,38 @@ func updateUsers(users []auth.AuthUser) {
|
||||
log.Infoln("Authentication of local server updated")
|
||||
}
|
||||
}
|
||||
|
||||
func updateProfile(cfg *config.Config) {
|
||||
profileCfg := cfg.Profile
|
||||
|
||||
profile.StoreSelected.Store(profileCfg.StoreSelected)
|
||||
if profileCfg.StoreSelected {
|
||||
patchSelectGroup(cfg.Proxies)
|
||||
}
|
||||
}
|
||||
|
||||
func patchSelectGroup(proxies map[string]C.Proxy) {
|
||||
mapping := cachefile.Cache().SelectedMap()
|
||||
if mapping == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for name, proxy := range proxies {
|
||||
outbound, ok := proxy.(*adapter.Proxy)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
selector, ok := outbound.ProxyAdapter.(*outboundgroup.Selector)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
selected, exist := mapping[name]
|
||||
if !exist {
|
||||
continue
|
||||
}
|
||||
|
||||
selector.Set(selected)
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
// When name is composed of a partial escape string, Golang does not unescape it
|
||||
|
@ -4,13 +4,15 @@ import (
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
"github.com/Dreamacro/clash/config"
|
||||
"github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/hub/executor"
|
||||
P "github.com/Dreamacro/clash/listener"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
P "github.com/Dreamacro/clash/proxy"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
)
|
||||
|
||||
@ -32,6 +34,7 @@ type configSchema struct {
|
||||
BindAddress *string `json:"bind-address"`
|
||||
Mode *tunnel.TunnelMode `json:"mode"`
|
||||
LogLevel *log.LogLevel `json:"log-level"`
|
||||
IPv6 *bool `json:"ipv6"`
|
||||
}
|
||||
|
||||
func getConfigs(w http.ResponseWriter, r *http.Request) {
|
||||
@ -64,11 +67,15 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
ports := P.GetPorts()
|
||||
P.ReCreateHTTP(pointerOrDefault(general.Port, ports.Port))
|
||||
P.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort))
|
||||
P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort))
|
||||
P.ReCreateTProxy(pointerOrDefault(general.TProxyPort, ports.TProxyPort))
|
||||
P.ReCreateMixed(pointerOrDefault(general.MixedPort, ports.MixedPort))
|
||||
|
||||
tcpIn := tunnel.TCPIn()
|
||||
udpIn := tunnel.UDPIn()
|
||||
|
||||
P.ReCreateHTTP(pointerOrDefault(general.Port, ports.Port), tcpIn)
|
||||
P.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort), tcpIn, udpIn)
|
||||
P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort), tcpIn, udpIn)
|
||||
P.ReCreateTProxy(pointerOrDefault(general.TProxyPort, ports.TProxyPort), tcpIn, udpIn)
|
||||
P.ReCreateMixed(pointerOrDefault(general.MixedPort, ports.MixedPort), tcpIn, udpIn)
|
||||
|
||||
if general.Mode != nil {
|
||||
tunnel.SetMode(*general.Mode)
|
||||
@ -78,6 +85,10 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) {
|
||||
log.SetLevel(*general.LogLevel)
|
||||
}
|
||||
|
||||
if general.IPv6 != nil {
|
||||
resolver.DisableIPv6 = !*general.IPv6
|
||||
}
|
||||
|
||||
render.NoContent(w, r)
|
||||
}
|
||||
|
||||
@ -106,6 +117,9 @@ func updateConfigs(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if req.Path == "" {
|
||||
req.Path = constant.Path.Config()
|
||||
}
|
||||
if !filepath.IsAbs(req.Path) {
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
render.JSON(w, r, newError("path is not a absolute path"))
|
||||
|
@ -7,11 +7,11 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
T "github.com/Dreamacro/clash/tunnel"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/Dreamacro/clash/tunnel/statistic"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
func connectionRouter() http.Handler {
|
||||
@ -24,7 +24,7 @@ func connectionRouter() http.Handler {
|
||||
|
||||
func getConnections(w http.ResponseWriter, r *http.Request) {
|
||||
if !websocket.IsWebSocketUpgrade(r) {
|
||||
snapshot := T.DefaultManager.Snapshot()
|
||||
snapshot := statistic.DefaultManager.Snapshot()
|
||||
render.JSON(w, r, snapshot)
|
||||
return
|
||||
}
|
||||
@ -50,7 +50,7 @@ func getConnections(w http.ResponseWriter, r *http.Request) {
|
||||
buf := &bytes.Buffer{}
|
||||
sendSnapshot := func() error {
|
||||
buf.Reset()
|
||||
snapshot := T.DefaultManager.Snapshot()
|
||||
snapshot := statistic.DefaultManager.Snapshot()
|
||||
if err := json.NewEncoder(buf).Encode(snapshot); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -73,7 +73,7 @@ func getConnections(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func closeConnection(w http.ResponseWriter, r *http.Request) {
|
||||
id := chi.URLParam(r, "id")
|
||||
snapshot := T.DefaultManager.Snapshot()
|
||||
snapshot := statistic.DefaultManager.Snapshot()
|
||||
for _, c := range snapshot.Connections {
|
||||
if id == c.ID() {
|
||||
c.Close()
|
||||
@ -84,7 +84,7 @@ func closeConnection(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func closeAllConnections(w http.ResponseWriter, r *http.Request) {
|
||||
snapshot := T.DefaultManager.Snapshot()
|
||||
snapshot := statistic.DefaultManager.Snapshot()
|
||||
for _, c := range snapshot.Connections {
|
||||
c.Close()
|
||||
}
|
||||
|
@ -4,10 +4,10 @@ import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/Dreamacro/clash/adapters/provider"
|
||||
"github.com/Dreamacro/clash/adapter/provider"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
)
|
||||
|
||||
|
@ -7,12 +7,13 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/adapters/outbound"
|
||||
"github.com/Dreamacro/clash/adapters/outboundgroup"
|
||||
"github.com/Dreamacro/clash/adapter"
|
||||
"github.com/Dreamacro/clash/adapter/outboundgroup"
|
||||
"github.com/Dreamacro/clash/component/profile/cachefile"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
)
|
||||
|
||||
@ -77,7 +78,7 @@ func updateProxy(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
proxy := r.Context().Value(CtxKeyProxy).(*outbound.Proxy)
|
||||
proxy := r.Context().Value(CtxKeyProxy).(*adapter.Proxy)
|
||||
selector, ok := proxy.ProxyAdapter.(*outboundgroup.Selector)
|
||||
if !ok {
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
@ -91,6 +92,7 @@ func updateProxy(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
cachefile.Cache().SetSelected(proxy.Name(), req.Name)
|
||||
render.NoContent(w, r)
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,7 @@ import (
|
||||
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
)
|
||||
|
||||
|
@ -9,9 +9,9 @@ import (
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
T "github.com/Dreamacro/clash/tunnel"
|
||||
"github.com/Dreamacro/clash/tunnel/statistic"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/cors"
|
||||
"github.com/go-chi/render"
|
||||
"github.com/gorilla/websocket"
|
||||
@ -143,7 +143,7 @@ func traffic(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
tick := time.NewTicker(time.Second)
|
||||
defer tick.Stop()
|
||||
t := T.DefaultManager
|
||||
t := statistic.DefaultManager
|
||||
buf := &bytes.Buffer{}
|
||||
var err error
|
||||
for range tick.C {
|
||||
|
39
listener/http/client.go
Normal file
39
listener/http/client.go
Normal file
@ -0,0 +1,39 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
|
||||
func newClient(source net.Addr, in chan<- C.ConnContext) *http.Client {
|
||||
return &http.Client{
|
||||
Transport: &http.Transport{
|
||||
// from http.DefaultTransport
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ResponseHeaderTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
DialContext: func(context context.Context, network, address string) (net.Conn, error) {
|
||||
if network != "tcp" && network != "tcp4" && network != "tcp6" {
|
||||
return nil, errors.New("unsupported network " + network)
|
||||
}
|
||||
|
||||
left, right := net.Pipe()
|
||||
|
||||
in <- inbound.NewHTTP(address, source, right)
|
||||
|
||||
return left, nil
|
||||
},
|
||||
},
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
},
|
||||
}
|
||||
}
|
10
listener/http/hack.go
Normal file
10
listener/http/hack.go
Normal file
@ -0,0 +1,10 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"net/http"
|
||||
_ "unsafe"
|
||||
)
|
||||
|
||||
//go:linkname ReadRequest net/http.readRequest
|
||||
func ReadRequest(b *bufio.Reader, deleteHostHeader bool) (req *http.Request, err error)
|
132
listener/http/proxy.go
Normal file
132
listener/http/proxy.go
Normal file
@ -0,0 +1,132 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
"github.com/Dreamacro/clash/common/cache"
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
authStore "github.com/Dreamacro/clash/listener/auth"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
)
|
||||
|
||||
func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.Cache) {
|
||||
client := newClient(c.RemoteAddr(), in)
|
||||
defer client.CloseIdleConnections()
|
||||
|
||||
conn := N.NewBufferedConn(c)
|
||||
|
||||
keepAlive := true
|
||||
trusted := cache == nil // disable authenticate if cache is nil
|
||||
|
||||
for keepAlive {
|
||||
request, err := ReadRequest(conn.Reader(), false)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
request.RemoteAddr = conn.RemoteAddr().String()
|
||||
|
||||
keepAlive = strings.TrimSpace(strings.ToLower(request.Header.Get("Proxy-Connection"))) == "keep-alive"
|
||||
|
||||
var resp *http.Response
|
||||
|
||||
if !trusted {
|
||||
resp = authenticate(request, cache)
|
||||
|
||||
trusted = resp == nil
|
||||
}
|
||||
|
||||
if trusted {
|
||||
if request.Method == http.MethodConnect {
|
||||
resp = responseWith(200)
|
||||
resp.Status = "Connection established"
|
||||
|
||||
if resp.Write(conn) != nil {
|
||||
break // close connection
|
||||
}
|
||||
|
||||
in <- inbound.NewHTTPS(request, conn)
|
||||
|
||||
return // hijack connection
|
||||
}
|
||||
|
||||
host := request.Header.Get("Host")
|
||||
if host != "" {
|
||||
request.Host = host
|
||||
}
|
||||
|
||||
request.RequestURI = ""
|
||||
|
||||
RemoveHopByHopHeaders(request.Header)
|
||||
RemoveExtraHTTPHostPort(request)
|
||||
|
||||
if request.URL.Scheme == "" || request.URL.Host == "" {
|
||||
resp = responseWith(http.StatusBadRequest)
|
||||
} else {
|
||||
resp, err = client.Do(request)
|
||||
if err != nil {
|
||||
resp = responseWith(http.StatusBadGateway)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RemoveHopByHopHeaders(resp.Header)
|
||||
|
||||
if keepAlive {
|
||||
resp.Header.Set("Proxy-Connection", "keep-alive")
|
||||
resp.Header.Set("Connection", "keep-alive")
|
||||
resp.Header.Set("Keep-Alive", "timeout=4")
|
||||
}
|
||||
|
||||
resp.Close = !keepAlive
|
||||
|
||||
err = resp.Write(conn)
|
||||
if err != nil {
|
||||
break // close connection
|
||||
}
|
||||
}
|
||||
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
func authenticate(request *http.Request, cache *cache.Cache) *http.Response {
|
||||
authenticator := authStore.Authenticator()
|
||||
if authenticator != nil {
|
||||
credential := ParseBasicProxyAuthorization(request)
|
||||
if credential == "" {
|
||||
resp := responseWith(http.StatusProxyAuthRequired)
|
||||
resp.Header.Set("Proxy-Authenticate", "Basic")
|
||||
return resp
|
||||
}
|
||||
|
||||
var authed interface{}
|
||||
if authed = cache.Get(credential); authed == nil {
|
||||
user, pass, err := DecodeBasicProxyAuthorization(credential)
|
||||
authed = err == nil && authenticator.Verify(user, pass)
|
||||
cache.Put(credential, authed, time.Minute)
|
||||
}
|
||||
if !authed.(bool) {
|
||||
log.Infoln("Auth failed from %s", request.RemoteAddr)
|
||||
|
||||
return responseWith(http.StatusForbidden)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func responseWith(statusCode int) *http.Response {
|
||||
return &http.Response{
|
||||
StatusCode: statusCode,
|
||||
Status: http.StatusText(statusCode),
|
||||
Proto: "HTTP/1.1",
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
Header: http.Header{},
|
||||
}
|
||||
}
|
59
listener/http/server.go
Normal file
59
listener/http/server.go
Normal file
@ -0,0 +1,59 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/cache"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
|
||||
type Listener struct {
|
||||
listener net.Listener
|
||||
address string
|
||||
closed bool
|
||||
}
|
||||
|
||||
func New(addr string, in chan<- C.ConnContext) (*Listener, error) {
|
||||
return NewWithAuthenticate(addr, in, true)
|
||||
}
|
||||
|
||||
func NewWithAuthenticate(addr string, in chan<- C.ConnContext, authenticate bool) (*Listener, error) {
|
||||
l, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var c *cache.Cache
|
||||
if authenticate {
|
||||
c = cache.New(time.Second * 30)
|
||||
}
|
||||
|
||||
hl := &Listener{
|
||||
listener: l,
|
||||
address: addr,
|
||||
}
|
||||
go func() {
|
||||
for {
|
||||
conn, err := hl.listener.Accept()
|
||||
if err != nil {
|
||||
if hl.closed {
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
go HandleConn(conn, in, c)
|
||||
}
|
||||
}()
|
||||
|
||||
return hl, nil
|
||||
}
|
||||
|
||||
func (l *Listener) Close() {
|
||||
l.closed = true
|
||||
l.listener.Close()
|
||||
}
|
||||
|
||||
func (l *Listener) Address() string {
|
||||
return l.address
|
||||
}
|
74
listener/http/utils.go
Normal file
74
listener/http/utils.go
Normal file
@ -0,0 +1,74 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// RemoveHopByHopHeaders remove hop-by-hop header
|
||||
func RemoveHopByHopHeaders(header http.Header) {
|
||||
// Strip hop-by-hop header based on RFC:
|
||||
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1
|
||||
// https://www.mnot.net/blog/2011/07/11/what_proxies_must_do
|
||||
|
||||
header.Del("Proxy-Connection")
|
||||
header.Del("Proxy-Authenticate")
|
||||
header.Del("Proxy-Authorization")
|
||||
header.Del("TE")
|
||||
header.Del("Trailers")
|
||||
header.Del("Transfer-Encoding")
|
||||
header.Del("Upgrade")
|
||||
|
||||
connections := header.Get("Connection")
|
||||
header.Del("Connection")
|
||||
if len(connections) == 0 {
|
||||
return
|
||||
}
|
||||
for _, h := range strings.Split(connections, ",") {
|
||||
header.Del(strings.TrimSpace(h))
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveExtraHTTPHostPort remove extra host port (example.com:80 --> example.com)
|
||||
// It resolves the behavior of some HTTP servers that do not handle host:80 (e.g. baidu.com)
|
||||
func RemoveExtraHTTPHostPort(req *http.Request) {
|
||||
host := req.Host
|
||||
if host == "" {
|
||||
host = req.URL.Host
|
||||
}
|
||||
|
||||
if pHost, port, err := net.SplitHostPort(host); err == nil && port == "80" {
|
||||
host = pHost
|
||||
}
|
||||
|
||||
req.Host = host
|
||||
req.URL.Host = host
|
||||
}
|
||||
|
||||
// ParseBasicProxyAuthorization parse header Proxy-Authorization and return base64-encoded credential
|
||||
func ParseBasicProxyAuthorization(request *http.Request) string {
|
||||
value := request.Header.Get("Proxy-Authorization")
|
||||
if !strings.HasPrefix(value, "Basic ") {
|
||||
return ""
|
||||
}
|
||||
|
||||
return value[6:] // value[len("Basic "):]
|
||||
}
|
||||
|
||||
// DecodeBasicProxyAuthorization decode base64-encoded credential
|
||||
func DecodeBasicProxyAuthorization(credential string) (string, string, error) {
|
||||
plain, err := base64.StdEncoding.DecodeString(credential)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
login := strings.Split(string(plain), ":")
|
||||
if len(login) != 2 {
|
||||
return "", "", errors.New("invalid login")
|
||||
}
|
||||
|
||||
return login[0], login[1], nil
|
||||
}
|
@ -6,26 +6,29 @@ import (
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/listener/http"
|
||||
"github.com/Dreamacro/clash/listener/mixed"
|
||||
"github.com/Dreamacro/clash/listener/redir"
|
||||
"github.com/Dreamacro/clash/listener/socks"
|
||||
"github.com/Dreamacro/clash/listener/tproxy"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/proxy/http"
|
||||
"github.com/Dreamacro/clash/proxy/mixed"
|
||||
"github.com/Dreamacro/clash/proxy/redir"
|
||||
"github.com/Dreamacro/clash/proxy/socks"
|
||||
)
|
||||
|
||||
var (
|
||||
allowLan = false
|
||||
bindAddress = "*"
|
||||
|
||||
socksListener *socks.SockListener
|
||||
socksUDPListener *socks.SockUDPListener
|
||||
httpListener *http.HttpListener
|
||||
redirListener *redir.RedirListener
|
||||
redirUDPListener *redir.RedirUDPListener
|
||||
tproxyListener *redir.TProxyListener
|
||||
tproxyUDPListener *redir.RedirUDPListener
|
||||
mixedListener *mixed.MixedListener
|
||||
mixedUDPLister *socks.SockUDPListener
|
||||
socksListener *socks.Listener
|
||||
socksUDPListener *socks.UDPListener
|
||||
httpListener *http.Listener
|
||||
redirListener *redir.Listener
|
||||
redirUDPListener *tproxy.UDPListener
|
||||
tproxyListener *tproxy.Listener
|
||||
tproxyUDPListener *tproxy.UDPListener
|
||||
mixedListener *mixed.Listener
|
||||
mixedUDPLister *socks.UDPListener
|
||||
|
||||
// lock for recreate function
|
||||
socksMux sync.Mutex
|
||||
@ -59,7 +62,7 @@ func SetBindAddress(host string) {
|
||||
bindAddress = host
|
||||
}
|
||||
|
||||
func ReCreateHTTP(port int) error {
|
||||
func ReCreateHTTP(port int, tcpIn chan<- C.ConnContext) error {
|
||||
httpMux.Lock()
|
||||
defer httpMux.Unlock()
|
||||
|
||||
@ -78,15 +81,16 @@ func ReCreateHTTP(port int) error {
|
||||
}
|
||||
|
||||
var err error
|
||||
httpListener, err = http.NewHttpProxy(addr)
|
||||
httpListener, err = http.New(addr, tcpIn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infoln("HTTP proxy listening at: %s", httpListener.Address())
|
||||
return nil
|
||||
}
|
||||
|
||||
func ReCreateSocks(port int) error {
|
||||
func ReCreateSocks(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) error {
|
||||
socksMux.Lock()
|
||||
defer socksMux.Unlock()
|
||||
|
||||
@ -121,12 +125,12 @@ func ReCreateSocks(port int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
tcpListener, err := socks.NewSocksProxy(addr)
|
||||
tcpListener, err := socks.New(addr, tcpIn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
udpListener, err := socks.NewSocksUDPProxy(addr)
|
||||
udpListener, err := socks.NewUDP(addr, udpIn)
|
||||
if err != nil {
|
||||
tcpListener.Close()
|
||||
return err
|
||||
@ -135,10 +139,11 @@ func ReCreateSocks(port int) error {
|
||||
socksListener = tcpListener
|
||||
socksUDPListener = udpListener
|
||||
|
||||
log.Infoln("SOCKS5 proxy listening at: %s", socksListener.Address())
|
||||
return nil
|
||||
}
|
||||
|
||||
func ReCreateRedir(port int) error {
|
||||
func ReCreateRedir(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) error {
|
||||
redirMux.Lock()
|
||||
defer redirMux.Unlock()
|
||||
|
||||
@ -165,20 +170,21 @@ func ReCreateRedir(port int) error {
|
||||
}
|
||||
|
||||
var err error
|
||||
redirListener, err = redir.NewRedirProxy(addr)
|
||||
redirListener, err = redir.New(addr, tcpIn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
redirUDPListener, err = redir.NewRedirUDPProxy(addr)
|
||||
redirUDPListener, err = tproxy.NewUDP(addr, udpIn)
|
||||
if err != nil {
|
||||
log.Warnln("Failed to start Redir UDP Listener: %s", err)
|
||||
}
|
||||
|
||||
log.Infoln("Redirect proxy listening at: %s", redirListener.Address())
|
||||
return nil
|
||||
}
|
||||
|
||||
func ReCreateTProxy(port int) error {
|
||||
func ReCreateTProxy(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) error {
|
||||
tproxyMux.Lock()
|
||||
defer tproxyMux.Unlock()
|
||||
|
||||
@ -205,20 +211,21 @@ func ReCreateTProxy(port int) error {
|
||||
}
|
||||
|
||||
var err error
|
||||
tproxyListener, err = redir.NewTProxy(addr)
|
||||
tproxyListener, err = tproxy.New(addr, tcpIn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tproxyUDPListener, err = redir.NewRedirUDPProxy(addr)
|
||||
tproxyUDPListener, err = tproxy.NewUDP(addr, udpIn)
|
||||
if err != nil {
|
||||
log.Warnln("Failed to start TProxy UDP Listener: %s", err)
|
||||
}
|
||||
|
||||
log.Infoln("TProxy server listening at: %s", tproxyListener.Address())
|
||||
return nil
|
||||
}
|
||||
|
||||
func ReCreateMixed(port int) error {
|
||||
func ReCreateMixed(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) error {
|
||||
mixedMux.Lock()
|
||||
defer mixedMux.Unlock()
|
||||
|
||||
@ -253,17 +260,18 @@ func ReCreateMixed(port int) error {
|
||||
}
|
||||
|
||||
var err error
|
||||
mixedListener, err = mixed.NewMixedProxy(addr)
|
||||
mixedListener, err = mixed.New(addr, tcpIn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mixedUDPLister, err = socks.NewSocksUDPProxy(addr)
|
||||
mixedUDPLister, err = socks.NewUDP(addr, udpIn)
|
||||
if err != nil {
|
||||
mixedListener.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infoln("Mixed(http+socks5) proxy listening at: %s", mixedListener.Address())
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -316,9 +324,8 @@ func genAddr(host string, port int, allowLan bool) string {
|
||||
if allowLan {
|
||||
if host == "*" {
|
||||
return fmt.Sprintf(":%d", port)
|
||||
} else {
|
||||
return fmt.Sprintf("%s:%d", host, port)
|
||||
}
|
||||
return fmt.Sprintf("%s:%d", host, port)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("127.0.0.1:%d", port)
|
67
listener/mixed/mixed.go
Normal file
67
listener/mixed/mixed.go
Normal file
@ -0,0 +1,67 @@
|
||||
package mixed
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/cache"
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/listener/http"
|
||||
"github.com/Dreamacro/clash/listener/socks"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
)
|
||||
|
||||
type Listener struct {
|
||||
listener net.Listener
|
||||
address string
|
||||
closed bool
|
||||
cache *cache.Cache
|
||||
}
|
||||
|
||||
func New(addr string, in chan<- C.ConnContext) (*Listener, error) {
|
||||
l, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ml := &Listener{l, addr, false, cache.New(30 * time.Second)}
|
||||
go func() {
|
||||
for {
|
||||
c, err := ml.listener.Accept()
|
||||
if err != nil {
|
||||
if ml.closed {
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
go handleConn(c, in, ml.cache)
|
||||
}
|
||||
}()
|
||||
|
||||
return ml, nil
|
||||
}
|
||||
|
||||
func (l *Listener) Close() {
|
||||
l.closed = true
|
||||
l.listener.Close()
|
||||
}
|
||||
|
||||
func (l *Listener) Address() string {
|
||||
return l.address
|
||||
}
|
||||
|
||||
func handleConn(conn net.Conn, in chan<- C.ConnContext, cache *cache.Cache) {
|
||||
bufConn := N.NewBufferedConn(conn)
|
||||
head, err := bufConn.Peek(1)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if head[0] == socks5.Version {
|
||||
socks.HandleSocks(bufConn, in)
|
||||
return
|
||||
}
|
||||
|
||||
http.HandleConn(bufConn, in, cache)
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user