Compare commits
62 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 |
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
|
3
.github/workflows/go.yml
vendored
3
.github/workflows/go.yml
vendored
@ -26,7 +26,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
go test ./...
|
go test ./...
|
||||||
go vet ./...
|
go vet ./...
|
||||||
go get -u honnef.co/go/tools/cmd/staticcheck
|
go install honnef.co/go/tools/cmd/staticcheck@latest
|
||||||
staticcheck -- $(go list ./...)
|
staticcheck -- $(go list ./...)
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
@ -44,4 +44,3 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
files: bin/*
|
files: bin/*
|
||||||
draft: true
|
draft: true
|
||||||
prerelease: true
|
|
||||||
|
6
Makefile
6
Makefile
@ -22,7 +22,8 @@ PLATFORM_LIST = \
|
|||||||
linux-mips64 \
|
linux-mips64 \
|
||||||
linux-mips64le \
|
linux-mips64le \
|
||||||
freebsd-386 \
|
freebsd-386 \
|
||||||
freebsd-amd64
|
freebsd-amd64 \
|
||||||
|
freebsd-arm64
|
||||||
|
|
||||||
WINDOWS_ARCH_LIST = \
|
WINDOWS_ARCH_LIST = \
|
||||||
windows-386 \
|
windows-386 \
|
||||||
@ -82,6 +83,9 @@ freebsd-386:
|
|||||||
freebsd-amd64:
|
freebsd-amd64:
|
||||||
GOARCH=amd64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
GOARCH=amd64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||||
|
|
||||||
|
freebsd-arm64:
|
||||||
|
GOARCH=arm64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||||
|
|
||||||
windows-386:
|
windows-386:
|
||||||
GOARCH=386 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
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
|
## Premium Release
|
||||||
[Release](https://github.com/Dreamacro/clash/releases/tag/premium)
|
[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
|
## Credits
|
||||||
|
|
||||||
* [riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2)
|
* [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] Redir proxy
|
||||||
- [x] UDP support
|
- [x] UDP support
|
||||||
- [x] Connection manager
|
- [x] Connection manager
|
||||||
- [ ] ~~Event API~~
|
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
package outbound
|
package adapter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/queue"
|
"github.com/Dreamacro/clash/common/queue"
|
||||||
@ -14,101 +15,25 @@ import (
|
|||||||
"go.uber.org/atomic"
|
"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 {
|
type Proxy struct {
|
||||||
C.ProxyAdapter
|
C.ProxyAdapter
|
||||||
history *queue.Queue
|
history *queue.Queue
|
||||||
alive *atomic.Bool
|
alive *atomic.Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Alive implements C.Proxy
|
||||||
func (p *Proxy) Alive() bool {
|
func (p *Proxy) Alive() bool {
|
||||||
return p.alive.Load()
|
return p.alive.Load()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dial implements C.Proxy
|
||||||
func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) {
|
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()
|
defer cancel()
|
||||||
return p.DialContext(ctx, metadata)
|
return p.DialContext(ctx, metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DialContext implements C.ProxyAdapter
|
||||||
func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||||
conn, err := p.ProxyAdapter.DialContext(ctx, metadata)
|
conn, err := p.ProxyAdapter.DialContext(ctx, metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -117,6 +42,7 @@ func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
|
|||||||
return conn, err
|
return conn, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DelayHistory implements C.Proxy
|
||||||
func (p *Proxy) DelayHistory() []C.DelayHistory {
|
func (p *Proxy) DelayHistory() []C.DelayHistory {
|
||||||
queue := p.history.Copy()
|
queue := p.history.Copy()
|
||||||
histories := []C.DelayHistory{}
|
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.
|
// 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) {
|
func (p *Proxy) LastDelay() (delay uint16) {
|
||||||
var max uint16 = 0xffff
|
var max uint16 = 0xffff
|
||||||
if !p.alive.Load() {
|
if !p.alive.Load() {
|
||||||
@ -144,6 +71,7 @@ func (p *Proxy) LastDelay() (delay uint16) {
|
|||||||
return history.Delay
|
return history.Delay
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements C.ProxyAdapter
|
||||||
func (p *Proxy) MarshalJSON() ([]byte, error) {
|
func (p *Proxy) MarshalJSON() ([]byte, error) {
|
||||||
inner, err := p.ProxyAdapter.MarshalJSON()
|
inner, err := p.ProxyAdapter.MarshalJSON()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -154,10 +82,12 @@ func (p *Proxy) MarshalJSON() ([]byte, error) {
|
|||||||
json.Unmarshal(inner, &mapping)
|
json.Unmarshal(inner, &mapping)
|
||||||
mapping["history"] = p.DelayHistory()
|
mapping["history"] = p.DelayHistory()
|
||||||
mapping["name"] = p.Name()
|
mapping["name"] = p.Name()
|
||||||
|
mapping["udp"] = p.SupportUDP()
|
||||||
return json.Marshal(mapping)
|
return json.Marshal(mapping)
|
||||||
}
|
}
|
||||||
|
|
||||||
// URLTest get the delay for the specified URL
|
// URLTest get the delay for the specified URL
|
||||||
|
// implements C.Proxy
|
||||||
func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
|
func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
p.alive.Store(err == nil)
|
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 {
|
func NewProxy(adapter C.ProxyAdapter) *Proxy {
|
||||||
return &Proxy{adapter, queue.New(10), atomic.NewBool(true)}
|
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)
|
||||||
|
}
|
@ -8,7 +8,7 @@ import (
|
|||||||
"github.com/Dreamacro/clash/context"
|
"github.com/Dreamacro/clash/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewHTTPS recieve CONNECT request and return ConnContext
|
// NewHTTPS receive CONNECT request and return ConnContext
|
||||||
func NewHTTPS(request *http.Request, conn net.Conn) *context.ConnContext {
|
func NewHTTPS(request *http.Request, conn net.Conn) *context.ConnContext {
|
||||||
metadata := parseHTTPAddr(request)
|
metadata := parseHTTPAddr(request)
|
||||||
metadata.Type = C.HTTPCONNECT
|
metadata.Type = C.HTTPCONNECT
|
@ -1,8 +1,8 @@
|
|||||||
package inbound
|
package inbound
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/Dreamacro/clash/component/socks5"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PacketAdapter is a UDP Packet adapter for socks/redir/tun
|
// PacketAdapter is a UDP Packet adapter for socks/redir/tun
|
@ -3,12 +3,12 @@ package inbound
|
|||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/socks5"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/context"
|
"github.com/Dreamacro/clash/context"
|
||||||
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewSocket recieve TCP inbound and return ConnContext
|
// NewSocket receive TCP inbound and return ConnContext
|
||||||
func NewSocket(target socks5.Addr, conn net.Conn, source C.Type) *context.ConnContext {
|
func NewSocket(target socks5.Addr, conn net.Conn, source C.Type) *context.ConnContext {
|
||||||
metadata := parseSocksAddr(target)
|
metadata := parseSocksAddr(target)
|
||||||
metadata.NetWork = C.TCP
|
metadata.NetWork = C.TCP
|
@ -6,8 +6,8 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/socks5"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseSocksAddr(target socks5.Addr) *C.Metadata {
|
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
|
*Base
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DialContext implements C.ProxyAdapter
|
||||||
func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||||
address := net.JoinHostPort(metadata.String(), metadata.DstPort)
|
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
|
return NewConn(c, d), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DialUDP implements C.ProxyAdapter
|
||||||
func (d *Direct) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
func (d *Direct) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||||
pc, err := dialer.ListenPacket("udp", "")
|
pc, err := dialer.ListenPacket("udp", "")
|
||||||
if err != nil {
|
if err != nil {
|
@ -35,6 +35,7 @@ type HttpOption struct {
|
|||||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StreamConn implements C.ProxyAdapter
|
||||||
func (h *Http) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
func (h *Http) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
if h.tlsConfig != nil {
|
if h.tlsConfig != nil {
|
||||||
cc := tls.Client(c, h.tlsConfig)
|
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
|
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)
|
c, err := dialer.DialContext(ctx, "tcp", h.addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
|
return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
|
||||||
}
|
}
|
||||||
tcpKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
|
|
||||||
|
defer safeConnClose(c, err)
|
||||||
|
|
||||||
c, err = h.StreamConn(c, metadata)
|
c, err = h.StreamConn(c, metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -121,7 +125,6 @@ func NewHttp(option HttpOption) *Http {
|
|||||||
}
|
}
|
||||||
tlsConfig = &tls.Config{
|
tlsConfig = &tls.Config{
|
||||||
InsecureSkipVerify: option.SkipCertVerify,
|
InsecureSkipVerify: option.SkipCertVerify,
|
||||||
ClientSessionCache: getClientSessionCache(),
|
|
||||||
ServerName: sni,
|
ServerName: sni,
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -14,10 +14,12 @@ type Reject struct {
|
|||||||
*Base
|
*Base
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DialContext implements C.ProxyAdapter
|
||||||
func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||||
return NewConn(&NopConn{}, r), nil
|
return NewConn(&NopConn{}, r), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DialUDP implements C.ProxyAdapter
|
||||||
func (r *Reject) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
func (r *Reject) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||||
return nil, errors.New("match reject rule")
|
return nil, errors.New("match reject rule")
|
||||||
}
|
}
|
@ -2,7 +2,6 @@ package outbound
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
@ -10,10 +9,10 @@ import (
|
|||||||
|
|
||||||
"github.com/Dreamacro/clash/common/structure"
|
"github.com/Dreamacro/clash/common/structure"
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"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"
|
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"
|
"github.com/Dreamacro/go-shadowsocks2/core"
|
||||||
)
|
)
|
||||||
@ -54,6 +53,7 @@ type v2rayObfsOption struct {
|
|||||||
Mux bool `obfs:"mux,omitempty"`
|
Mux bool `obfs:"mux,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StreamConn implements C.ProxyAdapter
|
||||||
func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
switch ss.obfsMode {
|
switch ss.obfsMode {
|
||||||
case "tls":
|
case "tls":
|
||||||
@ -73,17 +73,21 @@ func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, e
|
|||||||
return c, err
|
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)
|
c, err := dialer.DialContext(ctx, "tcp", ss.addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||||
}
|
}
|
||||||
tcpKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
|
|
||||||
|
defer safeConnClose(c, err)
|
||||||
|
|
||||||
c, err = ss.StreamConn(c, metadata)
|
c, err = ss.StreamConn(c, metadata)
|
||||||
return NewConn(c, ss), err
|
return NewConn(c, ss), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DialUDP implements C.ProxyAdapter
|
||||||
func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||||
pc, err := dialer.ListenPacket("udp", "")
|
pc, err := dialer.ListenPacket("udp", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -92,6 +96,7 @@ func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
|||||||
|
|
||||||
addr, err := resolveUDPAddr("udp", ss.addr)
|
addr, err := resolveUDPAddr("udp", ss.addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
pc.Close()
|
||||||
return nil, err
|
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
|
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) {
|
func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||||
cipher := option.Cipher
|
cipher := option.Cipher
|
||||||
@ -150,7 +149,6 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
|||||||
if opts.TLS {
|
if opts.TLS {
|
||||||
v2rayOption.TLS = true
|
v2rayOption.TLS = true
|
||||||
v2rayOption.SkipCertVerify = opts.SkipCertVerify
|
v2rayOption.SkipCertVerify = opts.SkipCertVerify
|
||||||
v2rayOption.SessionCache = getClientSessionCache()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2,15 +2,15 @@ package outbound
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"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"
|
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/core"
|
||||||
"github.com/Dreamacro/go-shadowsocks2/shadowaead"
|
"github.com/Dreamacro/go-shadowsocks2/shadowaead"
|
||||||
"github.com/Dreamacro/go-shadowsocks2/shadowstream"
|
"github.com/Dreamacro/go-shadowsocks2/shadowstream"
|
||||||
@ -36,6 +36,7 @@ type ShadowSocksROption struct {
|
|||||||
UDP bool `proxy:"udp,omitempty"`
|
UDP bool `proxy:"udp,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StreamConn implements C.ProxyAdapter
|
||||||
func (ssr *ShadowSocksR) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
func (ssr *ShadowSocksR) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
c = ssr.obfs.StreamConn(c)
|
c = ssr.obfs.StreamConn(c)
|
||||||
c = ssr.cipher.StreamConn(c)
|
c = ssr.cipher.StreamConn(c)
|
||||||
@ -57,17 +58,21 @@ func (ssr *ShadowSocksR) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn,
|
|||||||
return c, err
|
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)
|
c, err := dialer.DialContext(ctx, "tcp", ssr.addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", ssr.addr, err)
|
return nil, fmt.Errorf("%s connect error: %w", ssr.addr, err)
|
||||||
}
|
}
|
||||||
tcpKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
|
|
||||||
|
defer safeConnClose(c, err)
|
||||||
|
|
||||||
c, err = ssr.StreamConn(c, metadata)
|
c, err = ssr.StreamConn(c, metadata)
|
||||||
return NewConn(c, ssr), err
|
return NewConn(c, ssr), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DialUDP implements C.ProxyAdapter
|
||||||
func (ssr *ShadowSocksR) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
func (ssr *ShadowSocksR) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||||
pc, err := dialer.ListenPacket("udp", "")
|
pc, err := dialer.ListenPacket("udp", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -76,6 +81,7 @@ func (ssr *ShadowSocksR) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
|||||||
|
|
||||||
addr, err := resolveUDPAddr("udp", ssr.addr)
|
addr, err := resolveUDPAddr("udp", ssr.addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
pc.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,12 +90,6 @@ func (ssr *ShadowSocksR) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
|||||||
return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ssr), nil
|
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) {
|
func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
|
||||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||||
cipher := option.Cipher
|
cipher := option.Cipher
|
@ -8,9 +8,9 @@ import (
|
|||||||
|
|
||||||
"github.com/Dreamacro/clash/common/structure"
|
"github.com/Dreamacro/clash/common/structure"
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"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"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
obfs "github.com/Dreamacro/clash/transport/simple-obfs"
|
||||||
|
"github.com/Dreamacro/clash/transport/snell"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Snell struct {
|
type Snell struct {
|
||||||
@ -48,6 +48,7 @@ func streamConn(c net.Conn, option streamOption) *snell.Snell {
|
|||||||
return snell.StreamConn(c, option.psk, option.version)
|
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) {
|
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})
|
c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
|
||||||
port, _ := strconv.Atoi(metadata.DstPort)
|
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
|
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 {
|
if s.version == snell.Version2 {
|
||||||
c, err := s.pool.Get()
|
c, err := s.pool.Get()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -63,7 +65,10 @@ func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
|
|||||||
}
|
}
|
||||||
|
|
||||||
port, _ := strconv.Atoi(metadata.DstPort)
|
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
|
return NewConn(c, s), err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,6 +78,8 @@ func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
|
|||||||
}
|
}
|
||||||
tcpKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
|
|
||||||
|
defer safeConnClose(c, err)
|
||||||
|
|
||||||
c, err = s.StreamConn(c, metadata)
|
c, err = s.StreamConn(c, metadata)
|
||||||
return NewConn(c, s), err
|
return NewConn(c, s), err
|
||||||
}
|
}
|
@ -11,8 +11,8 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
"github.com/Dreamacro/clash/component/socks5"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Socks5 struct {
|
type Socks5 struct {
|
||||||
@ -35,6 +35,7 @@ type Socks5Option struct {
|
|||||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StreamConn implements C.ProxyAdapter
|
||||||
func (ss *Socks5) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
func (ss *Socks5) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
if ss.tls {
|
if ss.tls {
|
||||||
cc := tls.Client(c, ss.tlsConfig)
|
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
|
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)
|
c, err := dialer.DialContext(ctx, "tcp", ss.addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||||
}
|
}
|
||||||
tcpKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
|
|
||||||
|
defer safeConnClose(c, err)
|
||||||
|
|
||||||
c, err = ss.StreamConn(c, metadata)
|
c, err = ss.StreamConn(c, metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -73,8 +77,9 @@ func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn
|
|||||||
return NewConn(c, ss), nil
|
return NewConn(c, ss), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DialUDP implements C.ProxyAdapter
|
||||||
func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
|
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()
|
defer cancel()
|
||||||
c, err := dialer.DialContext(ctx, "tcp", ss.addr)
|
c, err := dialer.DialContext(ctx, "tcp", ss.addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -88,11 +93,7 @@ func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
|
|||||||
c = cc
|
c = cc
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
defer safeConnClose(c, err)
|
||||||
if err != nil {
|
|
||||||
c.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
tcpKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
var user *socks5.User
|
var user *socks5.User
|
||||||
@ -144,7 +145,6 @@ func NewSocks5(option Socks5Option) *Socks5 {
|
|||||||
if option.TLS {
|
if option.TLS {
|
||||||
tlsConfig = &tls.Config{
|
tlsConfig = &tls.Config{
|
||||||
InsecureSkipVerify: option.SkipCertVerify,
|
InsecureSkipVerify: option.SkipCertVerify,
|
||||||
ClientSessionCache: getClientSessionCache(),
|
|
||||||
ServerName: option.Server,
|
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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/tls"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/resolver"
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
"github.com/Dreamacro/clash/component/socks5"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
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) {
|
func tcpKeepAlive(c net.Conn) {
|
||||||
if tcp, ok := c.(*net.TCPConn); ok {
|
if tcp, ok := c.(*net.TCPConn); ok {
|
||||||
tcp.SetKeepAlive(true)
|
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 {
|
func serializesSocksAddr(metadata *C.Metadata) []byte {
|
||||||
var buf [][]byte
|
var buf [][]byte
|
||||||
aType := uint8(metadata.AddrType)
|
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))
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
@ -11,14 +12,22 @@ import (
|
|||||||
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
"github.com/Dreamacro/clash/component/resolver"
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
"github.com/Dreamacro/clash/component/vmess"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
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 {
|
type Vmess struct {
|
||||||
*Base
|
*Base
|
||||||
client *vmess.Client
|
client *vmess.Client
|
||||||
option *VmessOption
|
option *VmessOption
|
||||||
|
|
||||||
|
// for gun mux
|
||||||
|
gunTLSConfig *tls.Config
|
||||||
|
gunConfig *gun.Config
|
||||||
|
transport *http2.Transport
|
||||||
}
|
}
|
||||||
|
|
||||||
type VmessOption struct {
|
type VmessOption struct {
|
||||||
@ -33,6 +42,7 @@ type VmessOption struct {
|
|||||||
Network string `proxy:"network,omitempty"`
|
Network string `proxy:"network,omitempty"`
|
||||||
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
||||||
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
|
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
|
||||||
|
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
||||||
WSPath string `proxy:"ws-path,omitempty"`
|
WSPath string `proxy:"ws-path,omitempty"`
|
||||||
WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
|
WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
|
||||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||||
@ -50,6 +60,11 @@ type HTTP2Options struct {
|
|||||||
Path string `proxy:"path,omitempty"`
|
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) {
|
func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
var err error
|
var err error
|
||||||
switch v.option.Network {
|
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 {
|
if v.option.TLS {
|
||||||
wsOpts.TLS = true
|
wsOpts.TLS = true
|
||||||
wsOpts.SessionCache = getClientSessionCache()
|
|
||||||
wsOpts.SkipCertVerify = v.option.SkipCertVerify
|
wsOpts.SkipCertVerify = v.option.SkipCertVerify
|
||||||
wsOpts.ServerName = v.option.ServerName
|
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{
|
tlsOpts := &vmess.TLSConfig{
|
||||||
Host: host,
|
Host: host,
|
||||||
SkipCertVerify: v.option.SkipCertVerify,
|
SkipCertVerify: v.option.SkipCertVerify,
|
||||||
SessionCache: getClientSessionCache(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.option.ServerName != "" {
|
if v.option.ServerName != "" {
|
||||||
@ -110,7 +123,6 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
tlsOpts := vmess.TLSConfig{
|
tlsOpts := vmess.TLSConfig{
|
||||||
Host: host,
|
Host: host,
|
||||||
SkipCertVerify: v.option.SkipCertVerify,
|
SkipCertVerify: v.option.SkipCertVerify,
|
||||||
SessionCache: getClientSessionCache(),
|
|
||||||
NextProtos: []string{"h2"},
|
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)
|
c, err = vmess.StreamH2Conn(c, h2Opts)
|
||||||
|
case "grpc":
|
||||||
|
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig)
|
||||||
default:
|
default:
|
||||||
// handle TLS
|
// handle TLS
|
||||||
if v.option.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{
|
tlsOpts := &vmess.TLSConfig{
|
||||||
Host: host,
|
Host: host,
|
||||||
SkipCertVerify: v.option.SkipCertVerify,
|
SkipCertVerify: v.option.SkipCertVerify,
|
||||||
SessionCache: getClientSessionCache(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.option.ServerName != "" {
|
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))
|
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)
|
c, err := dialer.DialContext(ctx, "tcp", v.addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||||
}
|
}
|
||||||
tcpKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
|
defer safeConnClose(c, err)
|
||||||
|
|
||||||
c, err = v.StreamConn(c, metadata)
|
c, err = v.StreamConn(c, metadata)
|
||||||
return NewConn(c, v), err
|
return NewConn(c, v), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
// DialUDP implements C.ProxyAdapter
|
||||||
// vmess use stream-oriented udp, so clash needs a net.UDPAddr
|
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() {
|
if !metadata.Resolved() {
|
||||||
ip, err := resolver.ResolveIP(metadata.Host)
|
ip, err := resolver.ResolveIP(metadata.Host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -175,17 +207,33 @@ func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
|||||||
metadata.DstIP = ip
|
metadata.DstIP = ip
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
|
var c net.Conn
|
||||||
defer cancel()
|
// gun transport
|
||||||
c, err := dialer.DialContext(ctx, "tcp", v.addr)
|
if v.transport != nil {
|
||||||
if err != nil {
|
c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig)
|
||||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("new vmess client error: %v", err)
|
return nil, fmt.Errorf("new vmess client error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return newPacketConn(&vmessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil
|
return newPacketConn(&vmessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,15 +245,20 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
|||||||
Security: security,
|
Security: security,
|
||||||
HostName: option.Server,
|
HostName: option.Server,
|
||||||
Port: strconv.Itoa(option.Port),
|
Port: strconv.Itoa(option.Port),
|
||||||
|
IsAead: option.AlterID == 0,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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{
|
Base: &Base{
|
||||||
name: option.Name,
|
name: option.Name,
|
||||||
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
||||||
@ -214,7 +267,44 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
|||||||
},
|
},
|
||||||
client: client,
|
client: client,
|
||||||
option: &option,
|
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 {
|
func parseVmessAddr(metadata *C.Metadata) *vmess.DstAddr {
|
@ -3,7 +3,7 @@ package outboundgroup
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapters/provider"
|
"github.com/Dreamacro/clash/adapter/provider"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
)
|
)
|
||||||
|
|
@ -4,8 +4,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapters/outbound"
|
"github.com/Dreamacro/clash/adapter/outbound"
|
||||||
"github.com/Dreamacro/clash/adapters/provider"
|
"github.com/Dreamacro/clash/adapter/provider"
|
||||||
"github.com/Dreamacro/clash/common/singledo"
|
"github.com/Dreamacro/clash/common/singledo"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
)
|
)
|
||||||
@ -22,6 +22,7 @@ func (f *Fallback) Now() string {
|
|||||||
return proxy.Name()
|
return proxy.Name()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DialContext implements C.ProxyAdapter
|
||||||
func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||||
proxy := f.findAliveProxy(true)
|
proxy := f.findAliveProxy(true)
|
||||||
c, err := proxy.DialContext(ctx, metadata)
|
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
|
return c, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DialUDP implements C.ProxyAdapter
|
||||||
func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||||
proxy := f.findAliveProxy(true)
|
proxy := f.findAliveProxy(true)
|
||||||
pc, err := proxy.DialUDP(metadata)
|
pc, err := proxy.DialUDP(metadata)
|
||||||
@ -40,6 +42,7 @@ func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
|||||||
return pc, err
|
return pc, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SupportUDP implements C.ProxyAdapter
|
||||||
func (f *Fallback) SupportUDP() bool {
|
func (f *Fallback) SupportUDP() bool {
|
||||||
if f.disableUDP {
|
if f.disableUDP {
|
||||||
return false
|
return false
|
||||||
@ -49,6 +52,7 @@ func (f *Fallback) SupportUDP() bool {
|
|||||||
return proxy.SupportUDP()
|
return proxy.SupportUDP()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements C.ProxyAdapter
|
||||||
func (f *Fallback) MarshalJSON() ([]byte, error) {
|
func (f *Fallback) MarshalJSON() ([]byte, error) {
|
||||||
var all []string
|
var all []string
|
||||||
for _, proxy := range f.proxies(false) {
|
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 {
|
func (f *Fallback) Unwrap(metadata *C.Metadata) C.Proxy {
|
||||||
proxy := f.findAliveProxy(true)
|
proxy := f.findAliveProxy(true)
|
||||||
return proxy
|
return proxy
|
@ -7,8 +7,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapters/outbound"
|
"github.com/Dreamacro/clash/adapter/outbound"
|
||||||
"github.com/Dreamacro/clash/adapters/provider"
|
"github.com/Dreamacro/clash/adapter/provider"
|
||||||
"github.com/Dreamacro/clash/common/murmur3"
|
"github.com/Dreamacro/clash/common/murmur3"
|
||||||
"github.com/Dreamacro/clash/common/singledo"
|
"github.com/Dreamacro/clash/common/singledo"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
@ -68,6 +68,7 @@ func jumpHash(key uint64, buckets int32) int32 {
|
|||||||
return int32(b)
|
return int32(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DialContext implements C.ProxyAdapter
|
||||||
func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) {
|
func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -81,6 +82,7 @@ func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata) (c
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DialUDP implements C.ProxyAdapter
|
||||||
func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, err error) {
|
func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -93,6 +95,7 @@ func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, err error
|
|||||||
return proxy.DialUDP(metadata)
|
return proxy.DialUDP(metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SupportUDP implements C.ProxyAdapter
|
||||||
func (lb *LoadBalance) SupportUDP() bool {
|
func (lb *LoadBalance) SupportUDP() bool {
|
||||||
return !lb.disableUDP
|
return !lb.disableUDP
|
||||||
}
|
}
|
||||||
@ -130,6 +133,7 @@ func strategyConsistentHashing() strategyFn {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unwrap implements C.ProxyAdapter
|
||||||
func (lb *LoadBalance) Unwrap(metadata *C.Metadata) C.Proxy {
|
func (lb *LoadBalance) Unwrap(metadata *C.Metadata) C.Proxy {
|
||||||
proxies := lb.proxies(true)
|
proxies := lb.proxies(true)
|
||||||
return lb.strategyFn(proxies, metadata)
|
return lb.strategyFn(proxies, metadata)
|
||||||
@ -143,6 +147,7 @@ func (lb *LoadBalance) proxies(touch bool) []C.Proxy {
|
|||||||
return elm.([]C.Proxy)
|
return elm.([]C.Proxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements C.ProxyAdapter
|
||||||
func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
|
func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
|
||||||
var all []string
|
var all []string
|
||||||
for _, proxy := range lb.proxies(false) {
|
for _, proxy := range lb.proxies(false) {
|
@ -4,7 +4,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapters/provider"
|
"github.com/Dreamacro/clash/adapter/provider"
|
||||||
"github.com/Dreamacro/clash/common/structure"
|
"github.com/Dreamacro/clash/common/structure"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
)
|
)
|
@ -6,8 +6,8 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapters/outbound"
|
"github.com/Dreamacro/clash/adapter/outbound"
|
||||||
"github.com/Dreamacro/clash/adapters/provider"
|
"github.com/Dreamacro/clash/adapter/provider"
|
||||||
"github.com/Dreamacro/clash/common/singledo"
|
"github.com/Dreamacro/clash/common/singledo"
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
@ -19,6 +19,7 @@ type Relay struct {
|
|||||||
providers []provider.ProxyProvider
|
providers []provider.ProxyProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DialContext implements C.ProxyAdapter
|
||||||
func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||||
proxies := r.proxies(metadata, true)
|
proxies := r.proxies(metadata, true)
|
||||||
if len(proxies) == 0 {
|
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
|
return outbound.NewConn(c, r), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements C.ProxyAdapter
|
||||||
func (r *Relay) MarshalJSON() ([]byte, error) {
|
func (r *Relay) MarshalJSON() ([]byte, error) {
|
||||||
var all []string
|
var all []string
|
||||||
for _, proxy := range r.rawProxies(false) {
|
for _, proxy := range r.rawProxies(false) {
|
@ -5,8 +5,8 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapters/outbound"
|
"github.com/Dreamacro/clash/adapter/outbound"
|
||||||
"github.com/Dreamacro/clash/adapters/provider"
|
"github.com/Dreamacro/clash/adapter/provider"
|
||||||
"github.com/Dreamacro/clash/common/singledo"
|
"github.com/Dreamacro/clash/common/singledo"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
)
|
)
|
||||||
@ -19,6 +19,7 @@ type Selector struct {
|
|||||||
providers []provider.ProxyProvider
|
providers []provider.ProxyProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DialContext implements C.ProxyAdapter
|
||||||
func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||||
c, err := s.selectedProxy(true).DialContext(ctx, metadata)
|
c, err := s.selectedProxy(true).DialContext(ctx, metadata)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -27,6 +28,7 @@ func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Con
|
|||||||
return c, err
|
return c, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DialUDP implements C.ProxyAdapter
|
||||||
func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||||
pc, err := s.selectedProxy(true).DialUDP(metadata)
|
pc, err := s.selectedProxy(true).DialUDP(metadata)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -35,6 +37,7 @@ func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
|||||||
return pc, err
|
return pc, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SupportUDP implements C.ProxyAdapter
|
||||||
func (s *Selector) SupportUDP() bool {
|
func (s *Selector) SupportUDP() bool {
|
||||||
if s.disableUDP {
|
if s.disableUDP {
|
||||||
return false
|
return false
|
||||||
@ -43,6 +46,7 @@ func (s *Selector) SupportUDP() bool {
|
|||||||
return s.selectedProxy(false).SupportUDP()
|
return s.selectedProxy(false).SupportUDP()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements C.ProxyAdapter
|
||||||
func (s *Selector) MarshalJSON() ([]byte, error) {
|
func (s *Selector) MarshalJSON() ([]byte, error) {
|
||||||
var all []string
|
var all []string
|
||||||
for _, proxy := range getProvidersProxies(s.providers, false) {
|
for _, proxy := range getProvidersProxies(s.providers, false) {
|
||||||
@ -72,6 +76,7 @@ func (s *Selector) Set(name string) error {
|
|||||||
return errors.New("proxy not exist")
|
return errors.New("proxy not exist")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unwrap implements C.ProxyAdapter
|
||||||
func (s *Selector) Unwrap(metadata *C.Metadata) C.Proxy {
|
func (s *Selector) Unwrap(metadata *C.Metadata) C.Proxy {
|
||||||
return s.selectedProxy(true)
|
return s.selectedProxy(true)
|
||||||
}
|
}
|
@ -5,8 +5,8 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapters/outbound"
|
"github.com/Dreamacro/clash/adapter/outbound"
|
||||||
"github.com/Dreamacro/clash/adapters/provider"
|
"github.com/Dreamacro/clash/adapter/provider"
|
||||||
"github.com/Dreamacro/clash/common/singledo"
|
"github.com/Dreamacro/clash/common/singledo"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
)
|
)
|
||||||
@ -33,6 +33,7 @@ func (u *URLTest) Now() string {
|
|||||||
return u.fast(false).Name()
|
return u.fast(false).Name()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DialContext implements C.ProxyAdapter
|
||||||
func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) {
|
func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) {
|
||||||
c, err = u.fast(true).DialContext(ctx, metadata)
|
c, err = u.fast(true).DialContext(ctx, metadata)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -41,6 +42,7 @@ func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Co
|
|||||||
return c, err
|
return c, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DialUDP implements C.ProxyAdapter
|
||||||
func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||||
pc, err := u.fast(true).DialUDP(metadata)
|
pc, err := u.fast(true).DialUDP(metadata)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -49,6 +51,7 @@ func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
|||||||
return pc, err
|
return pc, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unwrap implements C.ProxyAdapter
|
||||||
func (u *URLTest) Unwrap(metadata *C.Metadata) C.Proxy {
|
func (u *URLTest) Unwrap(metadata *C.Metadata) C.Proxy {
|
||||||
return u.fast(true)
|
return u.fast(true)
|
||||||
}
|
}
|
||||||
@ -66,7 +69,13 @@ func (u *URLTest) fast(touch bool) C.Proxy {
|
|||||||
proxies := u.proxies(touch)
|
proxies := u.proxies(touch)
|
||||||
fast := proxies[0]
|
fast := proxies[0]
|
||||||
min := fast.LastDelay()
|
min := fast.LastDelay()
|
||||||
|
fastNotExist := true
|
||||||
|
|
||||||
for _, proxy := range proxies[1:] {
|
for _, proxy := range proxies[1:] {
|
||||||
|
if u.fastNode != nil && proxy.Name() == u.fastNode.Name() {
|
||||||
|
fastNotExist = false
|
||||||
|
}
|
||||||
|
|
||||||
if !proxy.Alive() {
|
if !proxy.Alive() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -79,7 +88,7 @@ func (u *URLTest) fast(touch bool) C.Proxy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// tolerance
|
// 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
|
u.fastNode = fast
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,6 +98,7 @@ func (u *URLTest) fast(touch bool) C.Proxy {
|
|||||||
return elm.(C.Proxy)
|
return elm.(C.Proxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SupportUDP implements C.ProxyAdapter
|
||||||
func (u *URLTest) SupportUDP() bool {
|
func (u *URLTest) SupportUDP() bool {
|
||||||
if u.disableUDP {
|
if u.disableUDP {
|
||||||
return false
|
return false
|
||||||
@ -97,6 +107,7 @@ func (u *URLTest) SupportUDP() bool {
|
|||||||
return u.fast(false).SupportUDP()
|
return u.fast(false).SupportUDP()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements C.ProxyAdapter
|
||||||
func (u *URLTest) MarshalJSON() ([]byte, error) {
|
func (u *URLTest) MarshalJSON() ([]byte, error) {
|
||||||
var all []string
|
var all []string
|
||||||
for _, proxy := range u.proxies(false) {
|
for _, proxy := range u.proxies(false) {
|
@ -16,25 +16,7 @@ func addrToMetadata(rawAddress string) (addr *C.Metadata, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ip := net.ParseIP(host)
|
ip := net.ParseIP(host)
|
||||||
if ip != nil {
|
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 {
|
|
||||||
addr = &C.Metadata{
|
addr = &C.Metadata{
|
||||||
AddrType: C.AtypDomainName,
|
AddrType: C.AtypDomainName,
|
||||||
Host: host,
|
Host: host,
|
||||||
@ -42,7 +24,23 @@ func addrToMetadata(rawAddress string) (addr *C.Metadata, err error) {
|
|||||||
DstPort: port,
|
DstPort: port,
|
||||||
}
|
}
|
||||||
return
|
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) {
|
func tcpKeepAlive(c net.Conn) {
|
@ -1,8 +1,9 @@
|
|||||||
package outbound
|
package adapter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/adapter/outbound"
|
||||||
"github.com/Dreamacro/clash/common/structure"
|
"github.com/Dreamacro/clash/common/structure"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
)
|
)
|
||||||
@ -20,36 +21,36 @@ func ParseProxy(mapping map[string]interface{}) (C.Proxy, error) {
|
|||||||
)
|
)
|
||||||
switch proxyType {
|
switch proxyType {
|
||||||
case "ss":
|
case "ss":
|
||||||
ssOption := &ShadowSocksOption{}
|
ssOption := &outbound.ShadowSocksOption{}
|
||||||
err = decoder.Decode(mapping, ssOption)
|
err = decoder.Decode(mapping, ssOption)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
proxy, err = NewShadowSocks(*ssOption)
|
proxy, err = outbound.NewShadowSocks(*ssOption)
|
||||||
case "ssr":
|
case "ssr":
|
||||||
ssrOption := &ShadowSocksROption{}
|
ssrOption := &outbound.ShadowSocksROption{}
|
||||||
err = decoder.Decode(mapping, ssrOption)
|
err = decoder.Decode(mapping, ssrOption)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
proxy, err = NewShadowSocksR(*ssrOption)
|
proxy, err = outbound.NewShadowSocksR(*ssrOption)
|
||||||
case "socks5":
|
case "socks5":
|
||||||
socksOption := &Socks5Option{}
|
socksOption := &outbound.Socks5Option{}
|
||||||
err = decoder.Decode(mapping, socksOption)
|
err = decoder.Decode(mapping, socksOption)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
proxy = NewSocks5(*socksOption)
|
proxy = outbound.NewSocks5(*socksOption)
|
||||||
case "http":
|
case "http":
|
||||||
httpOption := &HttpOption{}
|
httpOption := &outbound.HttpOption{}
|
||||||
err = decoder.Decode(mapping, httpOption)
|
err = decoder.Decode(mapping, httpOption)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
proxy = NewHttp(*httpOption)
|
proxy = outbound.NewHttp(*httpOption)
|
||||||
case "vmess":
|
case "vmess":
|
||||||
vmessOption := &VmessOption{
|
vmessOption := &outbound.VmessOption{
|
||||||
HTTPOpts: HTTPOptions{
|
HTTPOpts: outbound.HTTPOptions{
|
||||||
Method: "GET",
|
Method: "GET",
|
||||||
Path: []string{"/"},
|
Path: []string{"/"},
|
||||||
},
|
},
|
||||||
@ -58,21 +59,21 @@ func ParseProxy(mapping map[string]interface{}) (C.Proxy, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
proxy, err = NewVmess(*vmessOption)
|
proxy, err = outbound.NewVmess(*vmessOption)
|
||||||
case "snell":
|
case "snell":
|
||||||
snellOption := &SnellOption{}
|
snellOption := &outbound.SnellOption{}
|
||||||
err = decoder.Decode(mapping, snellOption)
|
err = decoder.Decode(mapping, snellOption)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
proxy, err = NewSnell(*snellOption)
|
proxy, err = outbound.NewSnell(*snellOption)
|
||||||
case "trojan":
|
case "trojan":
|
||||||
trojanOption := &TrojanOption{}
|
trojanOption := &outbound.TrojanOption{}
|
||||||
err = decoder.Decode(mapping, trojanOption)
|
err = decoder.Decode(mapping, trojanOption)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
proxy, err = NewTrojan(*trojanOption)
|
proxy, err = outbound.NewTrojan(*trojanOption)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unsupport proxy type: %s", proxyType)
|
return nil, fmt.Errorf("unsupport proxy type: %s", proxyType)
|
||||||
}
|
}
|
@ -72,6 +72,8 @@ func (f *fetcher) Initial() (interface{}, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isLocal = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.vehicle.Type() != File && !isLocal {
|
if f.vehicle.Type() != File && !isLocal {
|
@ -40,7 +40,7 @@ func ParseProxyProvider(name string, mapping map[string]interface{}) (ProxyProvi
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var hcInterval uint = 0
|
var hcInterval uint
|
||||||
if schema.HealthCheck.Enable {
|
if schema.HealthCheck.Enable {
|
||||||
hcInterval = uint(schema.HealthCheck.Interval)
|
hcInterval = uint(schema.HealthCheck.Interval)
|
||||||
}
|
}
|
@ -7,7 +7,7 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapters/outbound"
|
"github.com/Dreamacro/clash/adapter"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
@ -133,7 +133,7 @@ func proxiesParse(buf []byte) (interface{}, error) {
|
|||||||
|
|
||||||
proxies := []C.Proxy{}
|
proxies := []C.Proxy{}
|
||||||
for idx, mapping := range schema.Proxies {
|
for idx, mapping := range schema.Proxies {
|
||||||
proxy, err := outbound.ParseProxy(mapping)
|
proxy, err := adapter.ParseProxy(mapping)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("proxy %d error: %w", idx, err)
|
return nil, fmt.Errorf("proxy %d error: %w", idx, err)
|
||||||
}
|
}
|
@ -1,45 +0,0 @@
|
|||||||
package inbound
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
|
||||||
"github.com/Dreamacro/clash/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewHTTP recieve normal http request and return HTTPContext
|
|
||||||
func NewHTTP(request *http.Request, conn net.Conn) *context.HTTPContext {
|
|
||||||
metadata := parseHTTPAddr(request)
|
|
||||||
metadata.Type = C.HTTP
|
|
||||||
if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil {
|
|
||||||
metadata.SrcIP = ip
|
|
||||||
metadata.SrcPort = port
|
|
||||||
}
|
|
||||||
return context.NewHTTPContext(conn, request, metadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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,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 (
|
import (
|
||||||
"bufio"
|
"bufio"
|
@ -1,12 +1,13 @@
|
|||||||
package process
|
package process
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"net"
|
"net"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -94,12 +95,8 @@ func getExecPathFromPID(pid uint32) (string, error) {
|
|||||||
if errno != 0 {
|
if errno != 0 {
|
||||||
return "", errno
|
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 {
|
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
|
var spath string
|
||||||
isTCP := network == TCP
|
isTCP := network == TCP
|
||||||
switch network {
|
switch network {
|
||||||
@ -173,7 +177,7 @@ func (s *searcher) searchSocketPid(socket uint64) (uint32, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func newSearcher(major int) *searcher {
|
func newSearcher(major int) *searcher {
|
||||||
var s *searcher = nil
|
var s *searcher
|
||||||
switch major {
|
switch major {
|
||||||
case 11:
|
case 11:
|
||||||
s = &searcher{
|
s = &searcher{
|
||||||
@ -190,6 +194,8 @@ func newSearcher(major int) *searcher {
|
|||||||
udpInpOffset: 8,
|
udpInpOffset: 8,
|
||||||
}
|
}
|
||||||
case 12:
|
case 12:
|
||||||
|
fallthrough
|
||||||
|
case 13:
|
||||||
s = &searcher{
|
s = &searcher{
|
||||||
headSize: 64,
|
headSize: 64,
|
||||||
tcpItemSize: 744,
|
tcpItemSize: 744,
|
||||||
|
@ -22,8 +22,8 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
getExTcpTable uintptr
|
getExTCPTable uintptr
|
||||||
getExUdpTable uintptr
|
getExUDPTable uintptr
|
||||||
queryProcName uintptr
|
queryProcName uintptr
|
||||||
|
|
||||||
once sync.Once
|
once sync.Once
|
||||||
@ -35,12 +35,12 @@ func initWin32API() error {
|
|||||||
return fmt.Errorf("LoadLibrary iphlpapi.dll failed: %s", err.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 {
|
if err != nil {
|
||||||
return fmt.Errorf("GetProcAddress of %s failed: %s", tcpTableFunc, err.Error())
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("GetProcAddress of %s failed: %s", udpTableFunc, err.Error())
|
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
|
var fn uintptr
|
||||||
switch network {
|
switch network {
|
||||||
case TCP:
|
case TCP:
|
||||||
fn = getExTcpTable
|
fn = getExTCPTable
|
||||||
class = tcpTablePidConn
|
class = tcpTablePidConn
|
||||||
case UDP:
|
case UDP:
|
||||||
fn = getExUdpTable
|
fn = getExUDPTable
|
||||||
class = udpTablePid
|
class = udpTablePid
|
||||||
default:
|
default:
|
||||||
return "", ErrInvalidNetwork
|
return "", ErrInvalidNetwork
|
||||||
|
@ -26,7 +26,6 @@ type cache struct {
|
|||||||
type CacheFile struct {
|
type CacheFile struct {
|
||||||
path string
|
path string
|
||||||
model *cache
|
model *cache
|
||||||
enc *gob.Encoder
|
|
||||||
buf *bytes.Buffer
|
buf *bytes.Buffer
|
||||||
mux sync.Mutex
|
mux sync.Mutex
|
||||||
}
|
}
|
||||||
@ -39,15 +38,11 @@ func (c *CacheFile) SetSelected(group, selected string) {
|
|||||||
c.mux.Lock()
|
c.mux.Lock()
|
||||||
defer c.mux.Unlock()
|
defer c.mux.Unlock()
|
||||||
|
|
||||||
model, err := c.element()
|
model := c.element()
|
||||||
if err != nil {
|
|
||||||
log.Warnln("[CacheFile] read cache %s failed: %s", c.path, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
model.Selected[group] = selected
|
model.Selected[group] = selected
|
||||||
c.buf.Reset()
|
c.buf.Reset()
|
||||||
if err := c.enc.Encode(model); err != nil {
|
if err := gob.NewEncoder(c.buf).Encode(model); err != nil {
|
||||||
log.Warnln("[CacheFile] encode gob failed: %s", err.Error())
|
log.Warnln("[CacheFile] encode gob failed: %s", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -66,11 +61,7 @@ func (c *CacheFile) SelectedMap() map[string]string {
|
|||||||
c.mux.Lock()
|
c.mux.Lock()
|
||||||
defer c.mux.Unlock()
|
defer c.mux.Unlock()
|
||||||
|
|
||||||
model, err := c.element()
|
model := c.element()
|
||||||
if err != nil {
|
|
||||||
log.Warnln("[CacheFile] read cache %s failed: %s", c.path, err.Error())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
mapping := map[string]string{}
|
mapping := map[string]string{}
|
||||||
for k, v := range model.Selected {
|
for k, v := range model.Selected {
|
||||||
@ -79,9 +70,9 @@ func (c *CacheFile) SelectedMap() map[string]string {
|
|||||||
return mapping
|
return mapping
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CacheFile) element() (*cache, error) {
|
func (c *CacheFile) element() *cache {
|
||||||
if c.model != nil {
|
if c.model != nil {
|
||||||
return c.model, nil
|
return c.model
|
||||||
}
|
}
|
||||||
|
|
||||||
model := &cache{
|
model := &cache{
|
||||||
@ -90,24 +81,19 @@ func (c *CacheFile) element() (*cache, error) {
|
|||||||
|
|
||||||
if buf, err := ioutil.ReadFile(c.path); err == nil {
|
if buf, err := ioutil.ReadFile(c.path); err == nil {
|
||||||
bufReader := bytes.NewBuffer(buf)
|
bufReader := bytes.NewBuffer(buf)
|
||||||
dec := gob.NewDecoder(bufReader)
|
gob.NewDecoder(bufReader).Decode(model)
|
||||||
if err := dec.Decode(model); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.model = model
|
c.model = model
|
||||||
return c.model, nil
|
return c.model
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache return singleton of CacheFile
|
// Cache return singleton of CacheFile
|
||||||
func Cache() *CacheFile {
|
func Cache() *CacheFile {
|
||||||
initOnce.Do(func() {
|
initOnce.Do(func() {
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
defaultCache = &CacheFile{
|
defaultCache = &CacheFile{
|
||||||
path: C.Path.Cache(),
|
path: C.Path.Cache(),
|
||||||
buf: buf,
|
buf: &bytes.Buffer{},
|
||||||
enc: gob.NewEncoder(buf),
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
package resolver
|
package resolver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/trie"
|
"github.com/Dreamacro/clash/component/trie"
|
||||||
)
|
)
|
||||||
@ -18,6 +21,9 @@ var (
|
|||||||
|
|
||||||
// DefaultHosts aim to resolve hosts
|
// DefaultHosts aim to resolve hosts
|
||||||
DefaultHosts = trie.New()
|
DefaultHosts = trie.New()
|
||||||
|
|
||||||
|
// DefaultDNSTimeout defined the default dns request timeout
|
||||||
|
DefaultDNSTimeout = time.Second * 5
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -52,18 +58,16 @@ func ResolveIPv4(host string) (net.IP, error) {
|
|||||||
return DefaultResolver.ResolveIPv4(host)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
} else if len(ipAddrs) == 0 {
|
||||||
|
return nil, ErrIPNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ip := range ipAddrs {
|
return ipAddrs[rand.Intn(len(ipAddrs))], nil
|
||||||
if ip4 := ip.To4(); ip4 != nil {
|
|
||||||
return ip4, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, ErrIPNotFound
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResolveIPv6 with a host, return ipv6
|
// ResolveIPv6 with a host, return ipv6
|
||||||
@ -90,31 +94,29 @@ func ResolveIPv6(host string) (net.IP, error) {
|
|||||||
return DefaultResolver.ResolveIPv6(host)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
} else if len(ipAddrs) == 0 {
|
||||||
|
return nil, ErrIPNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ip := range ipAddrs {
|
return ipAddrs[rand.Intn(len(ipAddrs))], nil
|
||||||
if ip.To4() == nil {
|
|
||||||
return ip, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, ErrIPNotFound
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResolveIP with a host, return ip
|
// ResolveIPWithResolver same as ResolveIP, but with a resolver
|
||||||
func ResolveIP(host string) (net.IP, error) {
|
func ResolveIPWithResolver(host string, r Resolver) (net.IP, error) {
|
||||||
if node := DefaultHosts.Search(host); node != nil {
|
if node := DefaultHosts.Search(host); node != nil {
|
||||||
return node.Data.(net.IP), nil
|
return node.Data.(net.IP), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if DefaultResolver != nil {
|
if r != nil {
|
||||||
if DisableIPv6 {
|
if DisableIPv6 {
|
||||||
return DefaultResolver.ResolveIPv4(host)
|
return r.ResolveIPv4(host)
|
||||||
}
|
}
|
||||||
return DefaultResolver.ResolveIP(host)
|
return r.ResolveIP(host)
|
||||||
} else if DisableIPv6 {
|
} else if DisableIPv6 {
|
||||||
return ResolveIPv4(host)
|
return ResolveIPv4(host)
|
||||||
}
|
}
|
||||||
@ -131,3 +133,8 @@ func ResolveIP(host string) (net.IP, error) {
|
|||||||
|
|
||||||
return ipAddr.IP, nil
|
return ipAddr.IP, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ResolveIP with a host, return ip
|
||||||
|
func ResolveIP(host string) (net.IP, error) {
|
||||||
|
return ResolveIPWithResolver(host, DefaultResolver)
|
||||||
|
}
|
||||||
|
@ -23,7 +23,7 @@ type DomainTrie struct {
|
|||||||
root *Node
|
root *Node
|
||||||
}
|
}
|
||||||
|
|
||||||
func validAndSplitDomain(domain string) ([]string, bool) {
|
func ValidAndSplitDomain(domain string) ([]string, bool) {
|
||||||
if domain != "" && domain[len(domain)-1] == '.' {
|
if domain != "" && domain[len(domain)-1] == '.' {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
@ -54,7 +54,7 @@ func validAndSplitDomain(domain string) ([]string, bool) {
|
|||||||
// 4. .example.com
|
// 4. .example.com
|
||||||
// 5. +.example.com
|
// 5. +.example.com
|
||||||
func (t *DomainTrie) Insert(domain string, data interface{}) error {
|
func (t *DomainTrie) Insert(domain string, data interface{}) error {
|
||||||
parts, valid := validAndSplitDomain(domain)
|
parts, valid := ValidAndSplitDomain(domain)
|
||||||
if !valid {
|
if !valid {
|
||||||
return ErrInvalidDomain
|
return ErrInvalidDomain
|
||||||
}
|
}
|
||||||
@ -91,7 +91,7 @@ func (t *DomainTrie) insert(parts []string, data interface{}) {
|
|||||||
// 2. wildcard domain
|
// 2. wildcard domain
|
||||||
// 2. dot wildcard domain
|
// 2. dot wildcard domain
|
||||||
func (t *DomainTrie) Search(domain string) *Node {
|
func (t *DomainTrie) Search(domain string) *Node {
|
||||||
parts, valid := validAndSplitDomain(domain)
|
parts, valid := ValidAndSplitDomain(domain)
|
||||||
if !valid || parts[0] == "" {
|
if !valid || parts[0] == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -122,11 +122,7 @@ func (t *DomainTrie) search(node *Node, parts []string) *Node {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if c := node.getChild(dotWildcard); c != nil {
|
return node.getChild(dotWildcard)
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new, empty Trie.
|
// New returns a new, empty Trie.
|
||||||
|
@ -8,16 +8,17 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapters/outbound"
|
"github.com/Dreamacro/clash/adapter"
|
||||||
"github.com/Dreamacro/clash/adapters/outboundgroup"
|
"github.com/Dreamacro/clash/adapter/outbound"
|
||||||
"github.com/Dreamacro/clash/adapters/provider"
|
"github.com/Dreamacro/clash/adapter/outboundgroup"
|
||||||
|
"github.com/Dreamacro/clash/adapter/provider"
|
||||||
"github.com/Dreamacro/clash/component/auth"
|
"github.com/Dreamacro/clash/component/auth"
|
||||||
"github.com/Dreamacro/clash/component/fakeip"
|
"github.com/Dreamacro/clash/component/fakeip"
|
||||||
"github.com/Dreamacro/clash/component/trie"
|
"github.com/Dreamacro/clash/component/trie"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/dns"
|
"github.com/Dreamacro/clash/dns"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
R "github.com/Dreamacro/clash/rules"
|
R "github.com/Dreamacro/clash/rule"
|
||||||
T "github.com/Dreamacro/clash/tunnel"
|
T "github.com/Dreamacro/clash/tunnel"
|
||||||
|
|
||||||
yaml "gopkg.in/yaml.v2"
|
yaml "gopkg.in/yaml.v2"
|
||||||
@ -64,6 +65,7 @@ type DNS struct {
|
|||||||
DefaultNameserver []dns.NameServer `yaml:"default-nameserver"`
|
DefaultNameserver []dns.NameServer `yaml:"default-nameserver"`
|
||||||
FakeIPRange *fakeip.Pool
|
FakeIPRange *fakeip.Pool
|
||||||
Hosts *trie.DomainTrie
|
Hosts *trie.DomainTrie
|
||||||
|
NameServerPolicy map[string]dns.NameServer
|
||||||
}
|
}
|
||||||
|
|
||||||
// FallbackFilter config
|
// FallbackFilter config
|
||||||
@ -106,6 +108,7 @@ type RawDNS struct {
|
|||||||
FakeIPRange string `yaml:"fake-ip-range"`
|
FakeIPRange string `yaml:"fake-ip-range"`
|
||||||
FakeIPFilter []string `yaml:"fake-ip-filter"`
|
FakeIPFilter []string `yaml:"fake-ip-filter"`
|
||||||
DefaultNameserver []string `yaml:"default-nameserver"`
|
DefaultNameserver []string `yaml:"default-nameserver"`
|
||||||
|
NameServerPolicy map[string]string `yaml:"nameserver-policy"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RawFallbackFilter struct {
|
type RawFallbackFilter struct {
|
||||||
@ -272,13 +275,13 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
|||||||
groupsConfig := cfg.ProxyGroup
|
groupsConfig := cfg.ProxyGroup
|
||||||
providersConfig := cfg.ProxyProvider
|
providersConfig := cfg.ProxyProvider
|
||||||
|
|
||||||
proxies["DIRECT"] = outbound.NewProxy(outbound.NewDirect())
|
proxies["DIRECT"] = adapter.NewProxy(outbound.NewDirect())
|
||||||
proxies["REJECT"] = outbound.NewProxy(outbound.NewReject())
|
proxies["REJECT"] = adapter.NewProxy(outbound.NewReject())
|
||||||
proxyList = append(proxyList, "DIRECT", "REJECT")
|
proxyList = append(proxyList, "DIRECT", "REJECT")
|
||||||
|
|
||||||
// parse proxy
|
// parse proxy
|
||||||
for idx, mapping := range proxiesConfig {
|
for idx, mapping := range proxiesConfig {
|
||||||
proxy, err := outbound.ParseProxy(mapping)
|
proxy, err := adapter.ParseProxy(mapping)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("proxy %d: %w", idx, err)
|
return nil, nil, fmt.Errorf("proxy %d: %w", idx, err)
|
||||||
}
|
}
|
||||||
@ -337,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)
|
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
|
// initial compatible provider
|
||||||
@ -366,7 +369,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
|||||||
},
|
},
|
||||||
[]provider.ProxyProvider{pd},
|
[]provider.ProxyProvider{pd},
|
||||||
)
|
)
|
||||||
proxies["GLOBAL"] = outbound.NewProxy(global)
|
proxies["GLOBAL"] = adapter.NewProxy(global)
|
||||||
return proxies, providersMap, nil
|
return proxies, providersMap, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -500,6 +503,23 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) {
|
|||||||
return nameservers, nil
|
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) {
|
func parseFallbackIPCIDR(ips []string) ([]*net.IPNet, error) {
|
||||||
ipNets := []*net.IPNet{}
|
ipNets := []*net.IPNet{}
|
||||||
|
|
||||||
@ -537,6 +557,10 @@ func parseDNS(cfg RawDNS, hosts *trie.DomainTrie) (*DNS, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if dnsCfg.NameServerPolicy, err = parseNameServerPolicy(cfg.NameServerPolicy); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if len(cfg.DefaultNameserver) == 0 {
|
if len(cfg.DefaultNameserver) == 0 {
|
||||||
return nil, errors.New("default nameserver should have at least one nameserver")
|
return nil, errors.New("default nameserver should have at least one nameserver")
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,7 @@ func Init(dir string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't create file %s: %s", C.Path.Config(), err.Error())
|
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()
|
f.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapters/outboundgroup"
|
"github.com/Dreamacro/clash/adapter/outboundgroup"
|
||||||
"github.com/Dreamacro/clash/common/structure"
|
"github.com/Dreamacro/clash/common/structure"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -27,6 +27,10 @@ const (
|
|||||||
LoadBalance
|
LoadBalance
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DefaultTCPTimeout = 5 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
type Connection interface {
|
type Connection interface {
|
||||||
Chains() Chain
|
Chains() Chain
|
||||||
AppendToChains(adapter ProxyAdapter)
|
AppendToChains(adapter ProxyAdapter)
|
||||||
@ -69,8 +73,21 @@ type PacketConn interface {
|
|||||||
type ProxyAdapter interface {
|
type ProxyAdapter interface {
|
||||||
Name() string
|
Name() string
|
||||||
Type() AdapterType
|
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)
|
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)
|
DialContext(ctx context.Context, metadata *Metadata) (Conn, error)
|
||||||
|
|
||||||
DialUDP(metadata *Metadata) (PacketConn, error)
|
DialUDP(metadata *Metadata) (PacketConn, error)
|
||||||
SupportUDP() bool
|
SupportUDP() bool
|
||||||
MarshalJSON() ([]byte, error)
|
MarshalJSON() ([]byte, error)
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
package context
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
|
||||||
|
|
||||||
"github.com/gofrs/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
type HTTPContext struct {
|
|
||||||
id uuid.UUID
|
|
||||||
metadata *C.Metadata
|
|
||||||
conn net.Conn
|
|
||||||
req *http.Request
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHTTPContext(conn net.Conn, req *http.Request, metadata *C.Metadata) *HTTPContext {
|
|
||||||
id, _ := uuid.NewV4()
|
|
||||||
return &HTTPContext{
|
|
||||||
id: id,
|
|
||||||
metadata: metadata,
|
|
||||||
conn: conn,
|
|
||||||
req: req,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ID implement C.ConnContext ID
|
|
||||||
func (hc *HTTPContext) ID() uuid.UUID {
|
|
||||||
return hc.id
|
|
||||||
}
|
|
||||||
|
|
||||||
// Metadata implement C.ConnContext Metadata
|
|
||||||
func (hc *HTTPContext) Metadata() *C.Metadata {
|
|
||||||
return hc.metadata
|
|
||||||
}
|
|
||||||
|
|
||||||
// Conn implement C.ConnContext Conn
|
|
||||||
func (hc *HTTPContext) Conn() net.Conn {
|
|
||||||
return hc.conn
|
|
||||||
}
|
|
||||||
|
|
||||||
// Request return the http request struct
|
|
||||||
func (hc *HTTPContext) Request() *http.Request {
|
|
||||||
return hc.req
|
|
||||||
}
|
|
@ -7,6 +7,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
|
|
||||||
D "github.com/miekg/dns"
|
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
|
// a default ip dns
|
||||||
ip = net.ParseIP(c.host)
|
ip = net.ParseIP(c.host)
|
||||||
} else {
|
} else {
|
||||||
var err error
|
if ip, err = resolver.ResolveIPWithResolver(c.host, c.r); err != nil {
|
||||||
if ip, err = c.r.ResolveIP(c.host); err != nil {
|
|
||||||
return nil, fmt.Errorf("use default dns resolve failed: %w", err)
|
return nil, fmt.Errorf("use default dns resolve failed: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,12 +3,12 @@ package dns
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
|
|
||||||
D "github.com/miekg/dns"
|
D "github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
@ -75,7 +75,6 @@ func newDoHClient(url string, r *Resolver) *dohClient {
|
|||||||
return &dohClient{
|
return &dohClient{
|
||||||
url: url,
|
url: url,
|
||||||
transport: &http.Transport{
|
transport: &http.Transport{
|
||||||
TLSClientConfig: &tls.Config{ClientSessionCache: globalSessionCache},
|
|
||||||
ForceAttemptHTTP2: true,
|
ForceAttemptHTTP2: true,
|
||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
host, port, err := net.SplitHostPort(addr)
|
host, port, err := net.SplitHostPort(addr)
|
||||||
@ -83,12 +82,12 @@ func newDoHClient(url string, r *Resolver) *dohClient {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ip, err := r.ResolveIPv4(host)
|
ip, err := resolver.ResolveIPWithResolver(host, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return dialer.DialContext(ctx, "tcp4", net.JoinHostPort(ip.String(), port))
|
return dialer.DialContext(ctx, "tcp", net.JoinHostPort(ip.String(), port))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ package dns
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
@ -20,10 +19,6 @@ import (
|
|||||||
"golang.org/x/sync/singleflight"
|
"golang.org/x/sync/singleflight"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
globalSessionCache = tls.NewLRUClientSessionCache(64)
|
|
||||||
)
|
|
||||||
|
|
||||||
type dnsClient interface {
|
type dnsClient interface {
|
||||||
Exchange(m *D.Msg) (msg *D.Msg, err error)
|
Exchange(m *D.Msg) (msg *D.Msg, err error)
|
||||||
ExchangeContext(ctx context.Context, 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
|
fallbackIPFilters []fallbackIPFilter
|
||||||
group singleflight.Group
|
group singleflight.Group
|
||||||
lruCache *cache.LruCache
|
lruCache *cache.LruCache
|
||||||
|
policy *trie.DomainTrie
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResolveIP request with TypeA and TypeAAAA, priority return TypeA
|
// 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)
|
return r.ipExchange(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if matched := r.matchPolicy(m); len(matched) != 0 {
|
||||||
|
return r.batchExchange(matched, m)
|
||||||
|
}
|
||||||
return r.batchExchange(r.main, 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) {
|
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 {
|
for _, client := range clients {
|
||||||
r := client
|
r := client
|
||||||
fast.Go(func() (interface{}, error) {
|
fast.Go(func() (interface{}, error) {
|
||||||
@ -172,6 +171,24 @@ func (r *Resolver) batchExchange(clients []dnsClient, m *D.Msg) (msg *D.Msg, err
|
|||||||
return
|
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 {
|
func (r *Resolver) shouldOnlyQueryFallback(m *D.Msg) bool {
|
||||||
if r.fallback == nil || len(r.fallbackDomainFilters) == 0 {
|
if r.fallback == nil || len(r.fallbackDomainFilters) == 0 {
|
||||||
return false
|
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) {
|
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)
|
onlyFallback := r.shouldOnlyQueryFallback(m)
|
||||||
|
|
||||||
if onlyFallback {
|
if onlyFallback {
|
||||||
@ -293,6 +315,7 @@ type Config struct {
|
|||||||
FallbackFilter FallbackFilter
|
FallbackFilter FallbackFilter
|
||||||
Pool *fakeip.Pool
|
Pool *fakeip.Pool
|
||||||
Hosts *trie.DomainTrie
|
Hosts *trie.DomainTrie
|
||||||
|
Policy map[string]NameServer
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewResolver(config Config) *Resolver {
|
func NewResolver(config Config) *Resolver {
|
||||||
@ -312,6 +335,13 @@ func NewResolver(config Config) *Resolver {
|
|||||||
r.fallback = transform(config.Fallback, defaultResolver)
|
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{}
|
fallbackIPFilters := []fallbackIPFilter{}
|
||||||
if config.FallbackFilter.GeoIP {
|
if config.FallbackFilter.GeoIP {
|
||||||
fallbackIPFilters = append(fallbackIPFilters, &geoipFilter{})
|
fallbackIPFilters = append(fallbackIPFilters, &geoipFilter{})
|
||||||
|
@ -30,6 +30,7 @@ func (s *Server) ServeDNS(w D.ResponseWriter, r *D.Msg) {
|
|||||||
D.HandleFailed(w, r)
|
D.HandleFailed(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
msg.Compress = true
|
||||||
w.WriteMsg(msg)
|
w.WriteMsg(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,7 +127,6 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient {
|
|||||||
Client: &D.Client{
|
Client: &D.Client{
|
||||||
Net: s.Net,
|
Net: s.Net,
|
||||||
TLSConfig: &tls.Config{
|
TLSConfig: &tls.Config{
|
||||||
ClientSessionCache: globalSessionCache,
|
|
||||||
// alpn identifier, see https://tools.ietf.org/html/draft-hoffman-dprive-dns-tls-alpn-00#page-6
|
// alpn identifier, see https://tools.ietf.org/html/draft-hoffman-dprive-dns-tls-alpn-00#page-6
|
||||||
NextProtos: []string{"dns"},
|
NextProtos: []string{"dns"},
|
||||||
ServerName: host,
|
ServerName: host,
|
||||||
|
22
go.mod
22
go.mod
@ -3,20 +3,20 @@ module github.com/Dreamacro/clash
|
|||||||
go 1.16
|
go 1.16
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Dreamacro/go-shadowsocks2 v0.1.6
|
github.com/Dreamacro/go-shadowsocks2 v0.1.7
|
||||||
github.com/go-chi/chi v4.1.2+incompatible
|
github.com/go-chi/chi/v5 v5.0.3
|
||||||
github.com/go-chi/cors v1.1.1
|
github.com/go-chi/cors v1.2.0
|
||||||
github.com/go-chi/render v1.0.1
|
github.com/go-chi/render v1.0.1
|
||||||
github.com/gofrs/uuid v4.0.0+incompatible
|
github.com/gofrs/uuid v4.0.0+incompatible
|
||||||
github.com/gorilla/websocket v1.4.2
|
github.com/gorilla/websocket v1.4.2
|
||||||
github.com/miekg/dns v1.1.38
|
github.com/miekg/dns v1.1.43
|
||||||
github.com/oschwald/geoip2-golang v1.4.0
|
github.com/oschwald/geoip2-golang v1.5.0
|
||||||
github.com/sirupsen/logrus v1.8.0
|
github.com/sirupsen/logrus v1.8.1
|
||||||
github.com/stretchr/testify v1.7.0
|
github.com/stretchr/testify v1.7.0
|
||||||
go.uber.org/atomic v1.7.0
|
go.uber.org/atomic v1.8.0
|
||||||
golang.org/x/crypto v0.0.0-20210218145215-b8e89b74b9df
|
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e
|
||||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777
|
golang.org/x/net v0.0.0-20210614182718-04defd469f4e
|
||||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||||
golang.org/x/sys v0.0.0-20210218155724-8ebf48af031b
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
)
|
)
|
||||||
|
76
go.sum
76
go.sum
@ -1,71 +1,59 @@
|
|||||||
github.com/Dreamacro/go-shadowsocks2 v0.1.6 h1:PysSf9sLT3Qn8jhlin5v7Rk68gOQG4K5BZFY1nxLGxI=
|
github.com/Dreamacro/go-shadowsocks2 v0.1.7 h1:8CtbE1HoPPMfrQZGXmlluq6dO2lL31W6WRRE8fabc4Q=
|
||||||
github.com/Dreamacro/go-shadowsocks2 v0.1.6/go.mod h1:LSXCjyHesPY3pLjhwff1mQX72ItcBT/N2xNC685cYeU=
|
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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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/v5 v5.0.3 h1:khYQBdPivkYG1s1TAzDQG1f6eX4kD2TItYVZexL5rS4=
|
||||||
github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
github.com/go-chi/chi/v5 v5.0.3/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||||
github.com/go-chi/cors v1.1.1 h1:eHuqxsIw89iXcWnWUN8R72JMibABJTN/4IOYI5WERvw=
|
github.com/go-chi/cors v1.2.0 h1:tV1g1XENQ8ku4Bq3K9ub2AtgG+p16SmzeMSGTwrOKdE=
|
||||||
github.com/go-chi/cors v1.1.1/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I=
|
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 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8=
|
||||||
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
|
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
|
||||||
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
|
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/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 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/magefile/mage v1.10.0 h1:3HiXzCUY12kh9bIuyXShaVe529fJfyqoVM42o/uom2g=
|
github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
|
||||||
github.com/magefile/mage v1.10.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
|
||||||
github.com/miekg/dns v1.1.38 h1:MtIY+fmHUVVgv1AXzmKMWcwdCYxTRPG1EDjpqF4RCEw=
|
github.com/oschwald/geoip2-golang v1.5.0 h1:igg2yQIrrcRccB1ytFXqBfOHCjXWIoMv85lVJ1ONZzw=
|
||||||
github.com/miekg/dns v1.1.38/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
github.com/oschwald/geoip2-golang v1.5.0/go.mod h1:xdvYt5xQzB8ORWFqPnqMwZpCpgNagttWdoZLlJQzg7s=
|
||||||
github.com/oschwald/geoip2-golang v1.4.0 h1:5RlrjCgRyIGDz/mBmPfnAF4h8k0IAcRv9PvrpOfz+Ug=
|
github.com/oschwald/maxminddb-golang v1.8.0 h1:Uh/DSnGoxsyp/KYbY1AuP0tYEwfs0sCph9p/UMXK/Hk=
|
||||||
github.com/oschwald/geoip2-golang v1.4.0/go.mod h1:8QwxJvRImBH+Zl6Aa6MaIcs5YdlZSTKtzmPGzQqi9ng=
|
github.com/oschwald/maxminddb-golang v1.8.0/go.mod h1:RXZtst0N6+FY/3qCNmZMBApR19cdQj43/NM9VkrNAis=
|
||||||
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/sirupsen/logrus v1.8.0 h1:nfhvjKcUMhBMVqbKHJlk5RPrrfYr/NMo3692g0dwfWU=
|
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||||
github.com/sirupsen/logrus v1.8.0/go.mod h1:4GuYW9TZmE769R5STWrRakJc4UqQ3+QQ95fyz7ENv1A=
|
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/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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
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/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
go.uber.org/atomic v1.8.0 h1:CUhrE4N1rqSE6FM9ecihEjRkLQu8cDfgDyoOs83mEY4=
|
||||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
go.uber.org/atomic v1.8.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-20210317152858-513c2a44f670/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e h1:gsTQYXdTw2Gq7RBsWvlQ91b+aEQ6bXFUngBGuR8sPpI=
|
||||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20210218145215-b8e89b74b9df h1:y7QZzfUiTwWam+xBn29Ulb8CBwVN5UdzmMDavl9Whlw=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/crypto v0.0.0-20210218145215-b8e89b74b9df/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew=
|
|
||||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/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=
|
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
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-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210218155724-8ebf48af031b h1:lAZ0/chPUDWwjqosYR0X4M490zQhMsiJ4K3DbA7o+3g=
|
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210218155724-8ebf48af031b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/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-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/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
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.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-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 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
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 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
|
@ -6,9 +6,9 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapters/outbound"
|
"github.com/Dreamacro/clash/adapter"
|
||||||
"github.com/Dreamacro/clash/adapters/outboundgroup"
|
"github.com/Dreamacro/clash/adapter/outboundgroup"
|
||||||
"github.com/Dreamacro/clash/adapters/provider"
|
"github.com/Dreamacro/clash/adapter/provider"
|
||||||
"github.com/Dreamacro/clash/component/auth"
|
"github.com/Dreamacro/clash/component/auth"
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
"github.com/Dreamacro/clash/component/profile"
|
"github.com/Dreamacro/clash/component/profile"
|
||||||
@ -18,9 +18,9 @@ import (
|
|||||||
"github.com/Dreamacro/clash/config"
|
"github.com/Dreamacro/clash/config"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/dns"
|
"github.com/Dreamacro/clash/dns"
|
||||||
|
P "github.com/Dreamacro/clash/listener"
|
||||||
|
authStore "github.com/Dreamacro/clash/listener/auth"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
P "github.com/Dreamacro/clash/proxy"
|
|
||||||
authStore "github.com/Dreamacro/clash/proxy/auth"
|
|
||||||
"github.com/Dreamacro/clash/tunnel"
|
"github.com/Dreamacro/clash/tunnel"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -128,6 +128,7 @@ func updateDNS(c *config.DNS) {
|
|||||||
Domain: c.FallbackFilter.Domain,
|
Domain: c.FallbackFilter.Domain,
|
||||||
},
|
},
|
||||||
Default: c.DefaultNameserver,
|
Default: c.DefaultNameserver,
|
||||||
|
Policy: c.NameServerPolicy,
|
||||||
}
|
}
|
||||||
|
|
||||||
r := dns.NewResolver(cfg)
|
r := dns.NewResolver(cfg)
|
||||||
@ -186,23 +187,26 @@ func updateGeneral(general *config.General, force bool) {
|
|||||||
bindAddress := general.BindAddress
|
bindAddress := general.BindAddress
|
||||||
P.SetBindAddress(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())
|
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())
|
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())
|
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())
|
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())
|
log.Errorln("Start Mixed(http and socks5) server error: %s", err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -231,7 +235,7 @@ func patchSelectGroup(proxies map[string]C.Proxy) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for name, proxy := range proxies {
|
for name, proxy := range proxies {
|
||||||
outbound, ok := proxy.(*outbound.Proxy)
|
outbound, ok := proxy.(*adapter.Proxy)
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"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
|
// When name is composed of a partial escape string, Golang does not unescape it
|
||||||
|
@ -6,12 +6,13 @@ import (
|
|||||||
|
|
||||||
"github.com/Dreamacro/clash/component/resolver"
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
"github.com/Dreamacro/clash/config"
|
"github.com/Dreamacro/clash/config"
|
||||||
|
"github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/hub/executor"
|
"github.com/Dreamacro/clash/hub/executor"
|
||||||
|
P "github.com/Dreamacro/clash/listener"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
P "github.com/Dreamacro/clash/proxy"
|
|
||||||
"github.com/Dreamacro/clash/tunnel"
|
"github.com/Dreamacro/clash/tunnel"
|
||||||
|
|
||||||
"github.com/go-chi/chi"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/render"
|
"github.com/go-chi/render"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -66,11 +67,15 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ports := P.GetPorts()
|
ports := P.GetPorts()
|
||||||
P.ReCreateHTTP(pointerOrDefault(general.Port, ports.Port))
|
|
||||||
P.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort))
|
tcpIn := tunnel.TCPIn()
|
||||||
P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort))
|
udpIn := tunnel.UDPIn()
|
||||||
P.ReCreateTProxy(pointerOrDefault(general.TProxyPort, ports.TProxyPort))
|
|
||||||
P.ReCreateMixed(pointerOrDefault(general.MixedPort, ports.MixedPort))
|
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 {
|
if general.Mode != nil {
|
||||||
tunnel.SetMode(*general.Mode)
|
tunnel.SetMode(*general.Mode)
|
||||||
@ -112,6 +117,9 @@ func updateConfigs(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if req.Path == "" {
|
||||||
|
req.Path = constant.Path.Config()
|
||||||
|
}
|
||||||
if !filepath.IsAbs(req.Path) {
|
if !filepath.IsAbs(req.Path) {
|
||||||
render.Status(r, http.StatusBadRequest)
|
render.Status(r, http.StatusBadRequest)
|
||||||
render.JSON(w, r, newError("path is not a absolute path"))
|
render.JSON(w, r, newError("path is not a absolute path"))
|
||||||
|
@ -8,10 +8,10 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/tunnel/statistic"
|
"github.com/Dreamacro/clash/tunnel/statistic"
|
||||||
"github.com/gorilla/websocket"
|
|
||||||
|
|
||||||
"github.com/go-chi/chi"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/render"
|
"github.com/go-chi/render"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
)
|
)
|
||||||
|
|
||||||
func connectionRouter() http.Handler {
|
func connectionRouter() http.Handler {
|
||||||
|
@ -4,10 +4,10 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapters/provider"
|
"github.com/Dreamacro/clash/adapter/provider"
|
||||||
"github.com/Dreamacro/clash/tunnel"
|
"github.com/Dreamacro/clash/tunnel"
|
||||||
|
|
||||||
"github.com/go-chi/chi"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/render"
|
"github.com/go-chi/render"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -7,13 +7,13 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapters/outbound"
|
"github.com/Dreamacro/clash/adapter"
|
||||||
"github.com/Dreamacro/clash/adapters/outboundgroup"
|
"github.com/Dreamacro/clash/adapter/outboundgroup"
|
||||||
"github.com/Dreamacro/clash/component/profile/cachefile"
|
"github.com/Dreamacro/clash/component/profile/cachefile"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/tunnel"
|
"github.com/Dreamacro/clash/tunnel"
|
||||||
|
|
||||||
"github.com/go-chi/chi"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/render"
|
"github.com/go-chi/render"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -78,7 +78,7 @@ func updateProxy(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
proxy := r.Context().Value(CtxKeyProxy).(*outbound.Proxy)
|
proxy := r.Context().Value(CtxKeyProxy).(*adapter.Proxy)
|
||||||
selector, ok := proxy.ProxyAdapter.(*outboundgroup.Selector)
|
selector, ok := proxy.ProxyAdapter.(*outboundgroup.Selector)
|
||||||
if !ok {
|
if !ok {
|
||||||
render.Status(r, http.StatusBadRequest)
|
render.Status(r, http.StatusBadRequest)
|
||||||
|
@ -5,7 +5,7 @@ import (
|
|||||||
|
|
||||||
"github.com/Dreamacro/clash/tunnel"
|
"github.com/Dreamacro/clash/tunnel"
|
||||||
|
|
||||||
"github.com/go-chi/chi"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/render"
|
"github.com/go-chi/render"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ import (
|
|||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
"github.com/Dreamacro/clash/tunnel/statistic"
|
"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/cors"
|
||||||
"github.com/go-chi/render"
|
"github.com/go-chi/render"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
|
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"
|
"strconv"
|
||||||
"sync"
|
"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/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 (
|
var (
|
||||||
allowLan = false
|
allowLan = false
|
||||||
bindAddress = "*"
|
bindAddress = "*"
|
||||||
|
|
||||||
socksListener *socks.SockListener
|
socksListener *socks.Listener
|
||||||
socksUDPListener *socks.SockUDPListener
|
socksUDPListener *socks.UDPListener
|
||||||
httpListener *http.HttpListener
|
httpListener *http.Listener
|
||||||
redirListener *redir.RedirListener
|
redirListener *redir.Listener
|
||||||
redirUDPListener *redir.RedirUDPListener
|
redirUDPListener *tproxy.UDPListener
|
||||||
tproxyListener *redir.TProxyListener
|
tproxyListener *tproxy.Listener
|
||||||
tproxyUDPListener *redir.RedirUDPListener
|
tproxyUDPListener *tproxy.UDPListener
|
||||||
mixedListener *mixed.MixedListener
|
mixedListener *mixed.Listener
|
||||||
mixedUDPLister *socks.SockUDPListener
|
mixedUDPLister *socks.UDPListener
|
||||||
|
|
||||||
// lock for recreate function
|
// lock for recreate function
|
||||||
socksMux sync.Mutex
|
socksMux sync.Mutex
|
||||||
@ -59,7 +62,7 @@ func SetBindAddress(host string) {
|
|||||||
bindAddress = host
|
bindAddress = host
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReCreateHTTP(port int) error {
|
func ReCreateHTTP(port int, tcpIn chan<- C.ConnContext) error {
|
||||||
httpMux.Lock()
|
httpMux.Lock()
|
||||||
defer httpMux.Unlock()
|
defer httpMux.Unlock()
|
||||||
|
|
||||||
@ -78,15 +81,16 @@ func ReCreateHTTP(port int) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
httpListener, err = http.NewHttpProxy(addr)
|
httpListener, err = http.New(addr, tcpIn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Infoln("HTTP proxy listening at: %s", httpListener.Address())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReCreateSocks(port int) error {
|
func ReCreateSocks(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) error {
|
||||||
socksMux.Lock()
|
socksMux.Lock()
|
||||||
defer socksMux.Unlock()
|
defer socksMux.Unlock()
|
||||||
|
|
||||||
@ -121,12 +125,12 @@ func ReCreateSocks(port int) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
tcpListener, err := socks.NewSocksProxy(addr)
|
tcpListener, err := socks.New(addr, tcpIn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
udpListener, err := socks.NewSocksUDPProxy(addr)
|
udpListener, err := socks.NewUDP(addr, udpIn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tcpListener.Close()
|
tcpListener.Close()
|
||||||
return err
|
return err
|
||||||
@ -135,10 +139,11 @@ func ReCreateSocks(port int) error {
|
|||||||
socksListener = tcpListener
|
socksListener = tcpListener
|
||||||
socksUDPListener = udpListener
|
socksUDPListener = udpListener
|
||||||
|
|
||||||
|
log.Infoln("SOCKS5 proxy listening at: %s", socksListener.Address())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReCreateRedir(port int) error {
|
func ReCreateRedir(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) error {
|
||||||
redirMux.Lock()
|
redirMux.Lock()
|
||||||
defer redirMux.Unlock()
|
defer redirMux.Unlock()
|
||||||
|
|
||||||
@ -165,20 +170,21 @@ func ReCreateRedir(port int) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
redirListener, err = redir.NewRedirProxy(addr)
|
redirListener, err = redir.New(addr, tcpIn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
redirUDPListener, err = redir.NewRedirUDPProxy(addr)
|
redirUDPListener, err = tproxy.NewUDP(addr, udpIn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnln("Failed to start Redir UDP Listener: %s", err)
|
log.Warnln("Failed to start Redir UDP Listener: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Infoln("Redirect proxy listening at: %s", redirListener.Address())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReCreateTProxy(port int) error {
|
func ReCreateTProxy(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) error {
|
||||||
tproxyMux.Lock()
|
tproxyMux.Lock()
|
||||||
defer tproxyMux.Unlock()
|
defer tproxyMux.Unlock()
|
||||||
|
|
||||||
@ -205,20 +211,21 @@ func ReCreateTProxy(port int) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
tproxyListener, err = redir.NewTProxy(addr)
|
tproxyListener, err = tproxy.New(addr, tcpIn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
tproxyUDPListener, err = redir.NewRedirUDPProxy(addr)
|
tproxyUDPListener, err = tproxy.NewUDP(addr, udpIn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnln("Failed to start TProxy UDP Listener: %s", err)
|
log.Warnln("Failed to start TProxy UDP Listener: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Infoln("TProxy server listening at: %s", tproxyListener.Address())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReCreateMixed(port int) error {
|
func ReCreateMixed(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) error {
|
||||||
mixedMux.Lock()
|
mixedMux.Lock()
|
||||||
defer mixedMux.Unlock()
|
defer mixedMux.Unlock()
|
||||||
|
|
||||||
@ -253,17 +260,18 @@ func ReCreateMixed(port int) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
mixedListener, err = mixed.NewMixedProxy(addr)
|
mixedListener, err = mixed.New(addr, tcpIn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
mixedUDPLister, err = socks.NewSocksUDPProxy(addr)
|
mixedUDPLister, err = socks.NewUDP(addr, udpIn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mixedListener.Close()
|
mixedListener.Close()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Infoln("Mixed(http+socks5) proxy listening at: %s", mixedListener.Address())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -316,9 +324,8 @@ func genAddr(host string, port int, allowLan bool) string {
|
|||||||
if allowLan {
|
if allowLan {
|
||||||
if host == "*" {
|
if host == "*" {
|
||||||
return fmt.Sprintf(":%d", port)
|
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)
|
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)
|
||||||
|
}
|
56
listener/redir/tcp.go
Normal file
56
listener/redir/tcp.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package redir
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/adapter/inbound"
|
||||||
|
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) {
|
||||||
|
l, err := net.Listen("tcp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rl := &Listener{l, addr, false}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
c, err := l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
if rl.closed {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
go handleRedir(c, in)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return rl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) Close() {
|
||||||
|
l.closed = true
|
||||||
|
l.listener.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) Address() string {
|
||||||
|
return l.address
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleRedir(conn net.Conn, in chan<- C.ConnContext) {
|
||||||
|
target, err := parserPacket(conn)
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conn.(*net.TCPConn).SetKeepAlive(true)
|
||||||
|
in <- inbound.NewSocket(target, conn, C.REDIR)
|
||||||
|
}
|
@ -5,7 +5,7 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/socks5"
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
)
|
)
|
||||||
|
|
||||||
func parserPacket(c net.Conn) (socks5.Addr, error) {
|
func parserPacket(c net.Conn) (socks5.Addr, error) {
|
@ -6,7 +6,7 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/socks5"
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
@ -6,7 +6,7 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/socks5"
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
14
listener/redir/tcp_other.go
Normal file
14
listener/redir/tcp_other.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// +build !darwin,!linux,!freebsd
|
||||||
|
|
||||||
|
package redir
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
|
)
|
||||||
|
|
||||||
|
func parserPacket(conn net.Conn) (socks5.Addr, error) {
|
||||||
|
return nil, errors.New("system not support yet")
|
||||||
|
}
|
@ -5,29 +5,26 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
adapters "github.com/Dreamacro/clash/adapters/inbound"
|
"github.com/Dreamacro/clash/adapter/inbound"
|
||||||
"github.com/Dreamacro/clash/component/socks5"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/log"
|
authStore "github.com/Dreamacro/clash/listener/auth"
|
||||||
authStore "github.com/Dreamacro/clash/proxy/auth"
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
"github.com/Dreamacro/clash/tunnel"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type SockListener struct {
|
type Listener struct {
|
||||||
net.Listener
|
listener net.Listener
|
||||||
address string
|
address string
|
||||||
closed bool
|
closed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSocksProxy(addr string) (*SockListener, error) {
|
func New(addr string, in chan<- C.ConnContext) (*Listener, error) {
|
||||||
l, err := net.Listen("tcp", addr)
|
l, err := net.Listen("tcp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
sl := &SockListener{l, addr, false}
|
sl := &Listener{l, addr, false}
|
||||||
go func() {
|
go func() {
|
||||||
log.Infoln("SOCKS proxy listening at: %s", addr)
|
|
||||||
for {
|
for {
|
||||||
c, err := l.Accept()
|
c, err := l.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -36,23 +33,23 @@ func NewSocksProxy(addr string) (*SockListener, error) {
|
|||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
go HandleSocks(c)
|
go HandleSocks(c, in)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return sl, nil
|
return sl, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *SockListener) Close() {
|
func (l *Listener) Close() {
|
||||||
l.closed = true
|
l.closed = true
|
||||||
l.Listener.Close()
|
l.listener.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *SockListener) Address() string {
|
func (l *Listener) Address() string {
|
||||||
return l.address
|
return l.address
|
||||||
}
|
}
|
||||||
|
|
||||||
func HandleSocks(conn net.Conn) {
|
func HandleSocks(conn net.Conn, in chan<- C.ConnContext) {
|
||||||
target, command, err := socks5.ServerHandshake(conn, authStore.Authenticator())
|
target, command, err := socks5.ServerHandshake(conn, authStore.Authenticator())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
@ -66,5 +63,5 @@ func HandleSocks(conn net.Conn) {
|
|||||||
io.Copy(ioutil.Discard, conn)
|
io.Copy(ioutil.Discard, conn)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
tunnel.Add(adapters.NewSocket(target, conn, C.SOCKS))
|
in <- inbound.NewSocket(target, conn, C.SOCKS)
|
||||||
}
|
}
|
@ -3,33 +3,31 @@ package socks
|
|||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
adapters "github.com/Dreamacro/clash/adapters/inbound"
|
"github.com/Dreamacro/clash/adapter/inbound"
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
"github.com/Dreamacro/clash/common/sockopt"
|
"github.com/Dreamacro/clash/common/sockopt"
|
||||||
"github.com/Dreamacro/clash/component/socks5"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
"github.com/Dreamacro/clash/tunnel"
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SockUDPListener struct {
|
type UDPListener struct {
|
||||||
net.PacketConn
|
packetConn net.PacketConn
|
||||||
address string
|
address string
|
||||||
closed bool
|
closed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSocksUDPProxy(addr string) (*SockUDPListener, error) {
|
func NewUDP(addr string, in chan<- *inbound.PacketAdapter) (*UDPListener, error) {
|
||||||
l, err := net.ListenPacket("udp", addr)
|
l, err := net.ListenPacket("udp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = sockopt.UDPReuseaddr(l.(*net.UDPConn))
|
if err := sockopt.UDPReuseaddr(l.(*net.UDPConn)); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Warnln("Failed to Reuse UDP Address: %s", err)
|
log.Warnln("Failed to Reuse UDP Address: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
sl := &SockUDPListener{l, addr, false}
|
sl := &UDPListener{l, addr, false}
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
buf := pool.Get(pool.RelayBufferSize)
|
buf := pool.Get(pool.RelayBufferSize)
|
||||||
@ -41,23 +39,23 @@ func NewSocksUDPProxy(addr string) (*SockUDPListener, error) {
|
|||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
handleSocksUDP(l, buf[:n], remoteAddr)
|
handleSocksUDP(l, in, buf[:n], remoteAddr)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return sl, nil
|
return sl, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *SockUDPListener) Close() error {
|
func (l *UDPListener) Close() error {
|
||||||
l.closed = true
|
l.closed = true
|
||||||
return l.PacketConn.Close()
|
return l.packetConn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *SockUDPListener) Address() string {
|
func (l *UDPListener) Address() string {
|
||||||
return l.address
|
return l.address
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSocksUDP(pc net.PacketConn, buf []byte, addr net.Addr) {
|
func handleSocksUDP(pc net.PacketConn, in chan<- *inbound.PacketAdapter, buf []byte, addr net.Addr) {
|
||||||
target, payload, err := socks5.DecodeUDPPacket(buf)
|
target, payload, err := socks5.DecodeUDPPacket(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Unresolved UDP packet, return buffer to the pool
|
// Unresolved UDP packet, return buffer to the pool
|
||||||
@ -70,5 +68,8 @@ func handleSocksUDP(pc net.PacketConn, buf []byte, addr net.Addr) {
|
|||||||
payload: payload,
|
payload: payload,
|
||||||
bufRef: buf,
|
bufRef: buf,
|
||||||
}
|
}
|
||||||
tunnel.AddPacket(adapters.NewPacket(target, packet, C.SOCKS))
|
select {
|
||||||
|
case in <- inbound.NewPacket(target, packet, C.TPROXY):
|
||||||
|
default:
|
||||||
|
}
|
||||||
}
|
}
|
@ -4,7 +4,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
"github.com/Dreamacro/clash/component/socks5"
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
)
|
)
|
||||||
|
|
||||||
type packet struct {
|
type packet struct {
|
@ -1,4 +1,4 @@
|
|||||||
package redir
|
package tproxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
@ -1,6 +1,6 @@
|
|||||||
// +build linux
|
// +build linux
|
||||||
|
|
||||||
package redir
|
package tproxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
@ -1,6 +1,6 @@
|
|||||||
// +build !linux
|
// +build !linux
|
||||||
|
|
||||||
package redir
|
package tproxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
68
listener/tproxy/tproxy.go
Normal file
68
listener/tproxy/tproxy.go
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
package tproxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/adapter/inbound"
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Listener struct {
|
||||||
|
listener net.Listener
|
||||||
|
address string
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(addr string, in chan<- C.ConnContext) (*Listener, error) {
|
||||||
|
l, err := net.Listen("tcp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tl := l.(*net.TCPListener)
|
||||||
|
rc, err := tl.SyscallConn()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = setsockopt(rc, addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rl := &Listener{
|
||||||
|
listener: l,
|
||||||
|
address: addr,
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
c, err := l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
if rl.closed {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
go rl.handleTProxy(c, in)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return rl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) Close() {
|
||||||
|
l.closed = true
|
||||||
|
l.listener.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) Address() string {
|
||||||
|
return l.address
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) handleTProxy(conn net.Conn, in chan<- C.ConnContext) {
|
||||||
|
target := socks5.ParseAddrToSocksAddr(conn.LocalAddr())
|
||||||
|
conn.(*net.TCPConn).SetKeepAlive(true)
|
||||||
|
in <- inbound.NewSocket(target, conn, C.TPROXY)
|
||||||
|
}
|
@ -1,28 +1,27 @@
|
|||||||
package redir
|
package tproxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
adapters "github.com/Dreamacro/clash/adapters/inbound"
|
"github.com/Dreamacro/clash/adapter/inbound"
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
"github.com/Dreamacro/clash/component/socks5"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/tunnel"
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RedirUDPListener struct {
|
type UDPListener struct {
|
||||||
net.PacketConn
|
packetConn net.PacketConn
|
||||||
address string
|
address string
|
||||||
closed bool
|
closed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRedirUDPProxy(addr string) (*RedirUDPListener, error) {
|
func NewUDP(addr string, in chan<- *inbound.PacketAdapter) (*UDPListener, error) {
|
||||||
l, err := net.ListenPacket("udp", addr)
|
l, err := net.ListenPacket("udp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
rl := &RedirUDPListener{l, addr, false}
|
rl := &UDPListener{l, addr, false}
|
||||||
|
|
||||||
c := l.(*net.UDPConn)
|
c := l.(*net.UDPConn)
|
||||||
|
|
||||||
@ -53,27 +52,30 @@ func NewRedirUDPProxy(addr string) (*RedirUDPListener, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
handleRedirUDP(l, buf[:n], lAddr, rAddr)
|
handlePacketConn(l, in, buf[:n], lAddr, rAddr)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return rl, nil
|
return rl, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *RedirUDPListener) Close() error {
|
func (l *UDPListener) Close() error {
|
||||||
l.closed = true
|
l.closed = true
|
||||||
return l.PacketConn.Close()
|
return l.packetConn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *RedirUDPListener) Address() string {
|
func (l *UDPListener) Address() string {
|
||||||
return l.address
|
return l.address
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleRedirUDP(pc net.PacketConn, buf []byte, lAddr *net.UDPAddr, rAddr *net.UDPAddr) {
|
func handlePacketConn(pc net.PacketConn, in chan<- *inbound.PacketAdapter, buf []byte, lAddr *net.UDPAddr, rAddr *net.UDPAddr) {
|
||||||
target := socks5.ParseAddrToSocksAddr(rAddr)
|
target := socks5.ParseAddrToSocksAddr(rAddr)
|
||||||
pkt := &packet{
|
pkt := &packet{
|
||||||
lAddr: lAddr,
|
lAddr: lAddr,
|
||||||
buf: buf,
|
buf: buf,
|
||||||
}
|
}
|
||||||
tunnel.AddPacket(adapters.NewPacket(target, pkt, C.REDIR))
|
select {
|
||||||
|
case in <- inbound.NewPacket(target, pkt, C.TPROXY):
|
||||||
|
default:
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,8 +1,10 @@
|
|||||||
// +build linux
|
// +build linux
|
||||||
|
|
||||||
package redir
|
package tproxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
@ -10,6 +12,11 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
IPV6_TRANSPARENT = 0x4b
|
||||||
|
IPV6_RECVORIGDSTADDR = 0x4a
|
||||||
|
)
|
||||||
|
|
||||||
// dialUDP acts like net.DialUDP for transparent proxy.
|
// dialUDP acts like net.DialUDP for transparent proxy.
|
||||||
// It binds to a non-local address(`lAddr`).
|
// It binds to a non-local address(`lAddr`).
|
||||||
func dialUDP(network string, lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPConn, error) {
|
func dialUDP(network string, lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPConn, error) {
|
||||||
@ -94,3 +101,24 @@ func udpAddrFamily(net string, lAddr, rAddr *net.UDPAddr) int {
|
|||||||
}
|
}
|
||||||
return syscall.AF_INET6
|
return syscall.AF_INET6
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getOrigDst(oob []byte, oobn int) (*net.UDPAddr, error) {
|
||||||
|
msgs, err := syscall.ParseSocketControlMessage(oob[:oobn])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, msg := range msgs {
|
||||||
|
if msg.Header.Level == syscall.SOL_IP && msg.Header.Type == syscall.IP_RECVORIGDSTADDR {
|
||||||
|
ip := net.IP(msg.Data[4:8])
|
||||||
|
port := binary.BigEndian.Uint16(msg.Data[2:4])
|
||||||
|
return &net.UDPAddr{IP: ip, Port: int(port)}, nil
|
||||||
|
} else if msg.Header.Level == syscall.SOL_IPV6 && msg.Header.Type == IPV6_RECVORIGDSTADDR {
|
||||||
|
ip := net.IP(msg.Data[8:24])
|
||||||
|
port := binary.BigEndian.Uint16(msg.Data[2:4])
|
||||||
|
return &net.UDPAddr{IP: ip, Port: int(port)}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("cannot find origDst")
|
||||||
|
}
|
@ -1,12 +1,16 @@
|
|||||||
// +build !linux
|
// +build !linux
|
||||||
|
|
||||||
package redir
|
package tproxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func getOrigDst(oob []byte, oobn int) (*net.UDPAddr, error) {
|
||||||
|
return nil, errors.New("UDP redir not supported on current platform")
|
||||||
|
}
|
||||||
|
|
||||||
func dialUDP(network string, lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPConn, error) {
|
func dialUDP(network string, lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPConn, error) {
|
||||||
return nil, errors.New("UDP redir not supported on current platform")
|
return nil, errors.New("UDP redir not supported on current platform")
|
||||||
}
|
}
|
2
main.go
2
main.go
@ -45,7 +45,7 @@ func init() {
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if version {
|
if version {
|
||||||
fmt.Printf("Clash %s %s %s %s\n", C.Version, runtime.GOOS, runtime.GOARCH, C.BuildTime)
|
fmt.Printf("Clash %s %s %s with %s %s\n", C.Version, runtime.GOOS, runtime.GOARCH, runtime.Version(), C.BuildTime)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,114 +0,0 @@
|
|||||||
package http
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"encoding/base64"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
adapters "github.com/Dreamacro/clash/adapters/inbound"
|
|
||||||
"github.com/Dreamacro/clash/common/cache"
|
|
||||||
"github.com/Dreamacro/clash/component/auth"
|
|
||||||
"github.com/Dreamacro/clash/log"
|
|
||||||
authStore "github.com/Dreamacro/clash/proxy/auth"
|
|
||||||
"github.com/Dreamacro/clash/tunnel"
|
|
||||||
)
|
|
||||||
|
|
||||||
type HttpListener struct {
|
|
||||||
net.Listener
|
|
||||||
address string
|
|
||||||
closed bool
|
|
||||||
cache *cache.Cache
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHttpProxy(addr string) (*HttpListener, error) {
|
|
||||||
l, err := net.Listen("tcp", addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
hl := &HttpListener{l, addr, false, cache.New(30 * time.Second)}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
log.Infoln("HTTP proxy listening at: %s", addr)
|
|
||||||
|
|
||||||
for {
|
|
||||||
c, err := hl.Accept()
|
|
||||||
if err != nil {
|
|
||||||
if hl.closed {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
go HandleConn(c, hl.cache)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return hl, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *HttpListener) Close() {
|
|
||||||
l.closed = true
|
|
||||||
l.Listener.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *HttpListener) Address() string {
|
|
||||||
return l.address
|
|
||||||
}
|
|
||||||
|
|
||||||
func canActivate(loginStr string, authenticator auth.Authenticator, cache *cache.Cache) (ret bool) {
|
|
||||||
if result := cache.Get(loginStr); result != nil {
|
|
||||||
ret = result.(bool)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
loginData, err := base64.StdEncoding.DecodeString(loginStr)
|
|
||||||
login := strings.Split(string(loginData), ":")
|
|
||||||
ret = err == nil && len(login) == 2 && authenticator.Verify(login[0], login[1])
|
|
||||||
|
|
||||||
cache.Put(loginStr, ret, time.Minute)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleConn(conn net.Conn, cache *cache.Cache) {
|
|
||||||
br := bufio.NewReader(conn)
|
|
||||||
|
|
||||||
keepAlive:
|
|
||||||
request, err := http.ReadRequest(br)
|
|
||||||
if err != nil || request.URL.Host == "" {
|
|
||||||
conn.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
keepAlive := strings.TrimSpace(strings.ToLower(request.Header.Get("Proxy-Connection"))) == "keep-alive"
|
|
||||||
authenticator := authStore.Authenticator()
|
|
||||||
if authenticator != nil {
|
|
||||||
if authStrings := strings.Split(request.Header.Get("Proxy-Authorization"), " "); len(authStrings) != 2 {
|
|
||||||
conn.Write([]byte("HTTP/1.1 407 Proxy Authentication Required\r\nProxy-Authenticate: Basic\r\n\r\n"))
|
|
||||||
if keepAlive {
|
|
||||||
goto keepAlive
|
|
||||||
}
|
|
||||||
return
|
|
||||||
} else if !canActivate(authStrings[1], authenticator, cache) {
|
|
||||||
conn.Write([]byte("HTTP/1.1 403 Forbidden\r\n\r\n"))
|
|
||||||
log.Infoln("Auth failed from %s", conn.RemoteAddr().String())
|
|
||||||
if keepAlive {
|
|
||||||
goto keepAlive
|
|
||||||
}
|
|
||||||
conn.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if request.Method == http.MethodConnect {
|
|
||||||
_, err := conn.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n"))
|
|
||||||
if err != nil {
|
|
||||||
conn.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
tunnel.Add(adapters.NewHTTPS(request, conn))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tunnel.Add(adapters.NewHTTP(request, conn))
|
|
||||||
}
|
|
@ -1,69 +0,0 @@
|
|||||||
package mixed
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/cache"
|
|
||||||
"github.com/Dreamacro/clash/component/socks5"
|
|
||||||
"github.com/Dreamacro/clash/log"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/proxy/http"
|
|
||||||
"github.com/Dreamacro/clash/proxy/socks"
|
|
||||||
)
|
|
||||||
|
|
||||||
type MixedListener struct {
|
|
||||||
net.Listener
|
|
||||||
address string
|
|
||||||
closed bool
|
|
||||||
cache *cache.Cache
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewMixedProxy(addr string) (*MixedListener, error) {
|
|
||||||
l, err := net.Listen("tcp", addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ml := &MixedListener{l, addr, false, cache.New(30 * time.Second)}
|
|
||||||
go func() {
|
|
||||||
log.Infoln("Mixed(http+socks5) proxy listening at: %s", addr)
|
|
||||||
|
|
||||||
for {
|
|
||||||
c, err := ml.Accept()
|
|
||||||
if err != nil {
|
|
||||||
if ml.closed {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
go handleConn(c, ml.cache)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return ml, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *MixedListener) Close() {
|
|
||||||
l.closed = true
|
|
||||||
l.Listener.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *MixedListener) Address() string {
|
|
||||||
return l.address
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleConn(conn net.Conn, cache *cache.Cache) {
|
|
||||||
bufConn := NewBufferedConn(conn)
|
|
||||||
head, err := bufConn.Peek(1)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if head[0] == socks5.Version {
|
|
||||||
socks.HandleSocks(bufConn)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.HandleConn(bufConn, cache)
|
|
||||||
}
|
|
@ -1,59 +0,0 @@
|
|||||||
package redir
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapters/inbound"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
|
||||||
"github.com/Dreamacro/clash/log"
|
|
||||||
"github.com/Dreamacro/clash/tunnel"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RedirListener struct {
|
|
||||||
net.Listener
|
|
||||||
address string
|
|
||||||
closed bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRedirProxy(addr string) (*RedirListener, error) {
|
|
||||||
l, err := net.Listen("tcp", addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rl := &RedirListener{l, addr, false}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
log.Infoln("Redir proxy listening at: %s", addr)
|
|
||||||
for {
|
|
||||||
c, err := l.Accept()
|
|
||||||
if err != nil {
|
|
||||||
if rl.closed {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
go handleRedir(c)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return rl, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *RedirListener) Close() {
|
|
||||||
l.closed = true
|
|
||||||
l.Listener.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *RedirListener) Address() string {
|
|
||||||
return l.address
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleRedir(conn net.Conn) {
|
|
||||||
target, err := parserPacket(conn)
|
|
||||||
if err != nil {
|
|
||||||
conn.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
conn.(*net.TCPConn).SetKeepAlive(true)
|
|
||||||
tunnel.Add(inbound.NewSocket(target, conn, C.REDIR))
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
package redir
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/socks5"
|
|
||||||
)
|
|
||||||
|
|
||||||
func parserPacket(conn net.Conn) (socks5.Addr, error) {
|
|
||||||
return nil, errors.New("Windows not support yet")
|
|
||||||
}
|
|
@ -1,71 +0,0 @@
|
|||||||
package redir
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapters/inbound"
|
|
||||||
"github.com/Dreamacro/clash/component/socks5"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
|
||||||
"github.com/Dreamacro/clash/log"
|
|
||||||
"github.com/Dreamacro/clash/tunnel"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TProxyListener struct {
|
|
||||||
net.Listener
|
|
||||||
address string
|
|
||||||
closed bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTProxy(addr string) (*TProxyListener, error) {
|
|
||||||
l, err := net.Listen("tcp", addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
tl := l.(*net.TCPListener)
|
|
||||||
rc, err := tl.SyscallConn()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = setsockopt(rc, addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
rl := &TProxyListener{
|
|
||||||
Listener: l,
|
|
||||||
address: addr,
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
log.Infoln("TProxy server listening at: %s", addr)
|
|
||||||
for {
|
|
||||||
c, err := l.Accept()
|
|
||||||
if err != nil {
|
|
||||||
if rl.closed {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
go rl.handleRedir(c)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return rl, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *TProxyListener) Close() {
|
|
||||||
l.closed = true
|
|
||||||
l.Listener.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *TProxyListener) Address() string {
|
|
||||||
return l.address
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *TProxyListener) handleRedir(conn net.Conn) {
|
|
||||||
target := socks5.ParseAddrToSocksAddr(conn.LocalAddr())
|
|
||||||
conn.(*net.TCPConn).SetKeepAlive(true)
|
|
||||||
tunnel.Add(inbound.NewSocket(target, conn, C.TPROXY))
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
// +build linux
|
|
||||||
|
|
||||||
package redir
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"net"
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
IPV6_TRANSPARENT = 0x4b
|
|
||||||
IPV6_RECVORIGDSTADDR = 0x4a
|
|
||||||
)
|
|
||||||
|
|
||||||
func getOrigDst(oob []byte, oobn int) (*net.UDPAddr, error) {
|
|
||||||
msgs, err := syscall.ParseSocketControlMessage(oob[:oobn])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, msg := range msgs {
|
|
||||||
if msg.Header.Level == syscall.SOL_IP && msg.Header.Type == syscall.IP_RECVORIGDSTADDR {
|
|
||||||
ip := net.IP(msg.Data[4:8])
|
|
||||||
port := binary.BigEndian.Uint16(msg.Data[2:4])
|
|
||||||
return &net.UDPAddr{IP: ip, Port: int(port)}, nil
|
|
||||||
} else if msg.Header.Level == syscall.SOL_IPV6 && msg.Header.Type == IPV6_RECVORIGDSTADDR {
|
|
||||||
ip := net.IP(msg.Data[8:24])
|
|
||||||
port := binary.BigEndian.Uint16(msg.Data[2:4])
|
|
||||||
return &net.UDPAddr{IP: ip, Port: int(port)}, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.New("cannot find origDst")
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
// +build !linux
|
|
||||||
|
|
||||||
package redir
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
func getOrigDst(oob []byte, oobn int) (*net.UDPAddr, error) {
|
|
||||||
return nil, errors.New("UDP redir not supported on current platform")
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user