Compare commits
19 Commits
Author | SHA1 | Date | |
---|---|---|---|
88c7ccf749 | |||
3cacfb8a7f | |||
0eef9bbf5d | |||
39b45513af | |||
283e4e1f4f | |||
bd0a1ae1a7 | |||
018a6ba041 | |||
9c2ace1f91 | |||
17224a32cb | |||
05ab653103 | |||
c2c8f82f96 | |||
330a3391e3 | |||
79e5338113 | |||
bc4ca2818b | |||
46b8e2eec1 | |||
cd9f496fc9 | |||
ef301af04f | |||
6ffbb7c89e | |||
961250f998 |
@ -11,12 +11,8 @@ env:
|
||||
- NAME=clash
|
||||
- BINDIR=bin
|
||||
script:
|
||||
- go test
|
||||
- GOARCH=amd64 GOOS=linux go build -o $BINDIR/$NAME-linux
|
||||
- GOARCH=amd64 GOOS=darwin go build -o $BINDIR/$NAME-macos
|
||||
- chmod +x $BINDIR/$NAME-*
|
||||
- gzip $BINDIR/$NAME-linux
|
||||
- gzip $BINDIR/$NAME-macos
|
||||
- go test ./...
|
||||
before_deploy: make -j releases
|
||||
deploy:
|
||||
provider: releases
|
||||
prerelease: true
|
||||
|
19
Dockerfile
Normal file
19
Dockerfile
Normal file
@ -0,0 +1,19 @@
|
||||
FROM golang:latest as builder
|
||||
RUN wget http://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.tar.gz -O /tmp/GeoLite2-Country.tar.gz && \
|
||||
tar zxvf /tmp/GeoLite2-Country.tar.gz -C /tmp && \
|
||||
cp /tmp/GeoLite2-Country_*/GeoLite2-Country.mmdb /Country.mmdb
|
||||
RUN curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh && \
|
||||
mkdir -p /go/src/github.com/Dreamacro/clash
|
||||
WORKDIR /go/src/github.com/Dreamacro/clash
|
||||
COPY . /go/src/github.com/Dreamacro/clash
|
||||
RUN dep ensure && \
|
||||
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '-w -s' -o /clash && \
|
||||
chmod +x /clash
|
||||
|
||||
FROM alpine:latest
|
||||
RUN apk --no-cache add ca-certificates && \
|
||||
mkdir -p /root/.config/clash
|
||||
COPY --from=builder /Country.mmdb /root/.config/clash/
|
||||
COPY --from=builder /clash .
|
||||
EXPOSE 7890 7891
|
||||
ENTRYPOINT ["/clash"]
|
19
Gopkg.lock
generated
19
Gopkg.lock
generated
@ -13,6 +13,18 @@
|
||||
revision = "44cc805cf13205b55f69e14bcb69867d1ae92f98"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/go-chi/chi"
|
||||
packages = ["."]
|
||||
revision = "e83ac2304db3c50cf03d96a2fcd39009d458bc35"
|
||||
version = "v3.3.2"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/go-chi/render"
|
||||
packages = ["."]
|
||||
revision = "3215478343fbc559bd3fc08f7031bb134d6bdad5"
|
||||
version = "v1.0.1"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/oschwald/geoip2-golang"
|
||||
packages = ["."]
|
||||
@ -49,10 +61,11 @@
|
||||
"chacha20poly1305",
|
||||
"hkdf",
|
||||
"internal/chacha20",
|
||||
"internal/subtle",
|
||||
"poly1305",
|
||||
"ssh/terminal"
|
||||
]
|
||||
revision = "8ac0e0d97ce45cd83d1d7243c060cb8461dda5e9"
|
||||
revision = "027cca12c2d63e3d62b670d901e8a2c95854feec"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
@ -62,7 +75,7 @@
|
||||
"unix",
|
||||
"windows"
|
||||
]
|
||||
revision = "9527bec2660bd847c050fda93a0f0c6dee0800bb"
|
||||
revision = "6c888cc515d3ed83fc103cf1d84468aad274b0a7"
|
||||
|
||||
[[projects]]
|
||||
name = "gopkg.in/eapache/channels.v1"
|
||||
@ -79,6 +92,6 @@
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "4297c505508c6cdd8c94fd4ef29bc6940c65fea81e125fcf871a316b6e671a71"
|
||||
inputs-digest = "c3c901e4e393a2df9e421924d3a4d85ec73642e36dcbc1ddca5fc13159220e86"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
@ -25,6 +25,14 @@
|
||||
# unused-packages = true
|
||||
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/go-chi/chi"
|
||||
version = "3.3.2"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/go-chi/render"
|
||||
version = "1.0.1"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/oschwald/geoip2-golang"
|
||||
version = "1.2.1"
|
||||
|
23
Makefile
Normal file
23
Makefile
Normal file
@ -0,0 +1,23 @@
|
||||
NAME=clash
|
||||
BINDIR=bin
|
||||
GOBUILD=CGO_ENABLED=0 go build -ldflags '-w -s'
|
||||
|
||||
all: linux macos
|
||||
|
||||
linux:
|
||||
GOARCH=amd64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
macos:
|
||||
GOARCH=amd64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
win64:
|
||||
GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
||||
|
||||
releases: linux macos win64
|
||||
chmod +x $(BINDIR)/$(NAME)-*
|
||||
gzip $(BINDIR)/$(NAME)-linux
|
||||
gzip $(BINDIR)/$(NAME)-macos
|
||||
zip -m -j $(BINDIR)/$(NAME)-win64.zip $(BINDIR)/$(NAME)-win64.exe
|
||||
|
||||
clean:
|
||||
rm $(BINDIR)/*
|
43
README.md
43
README.md
@ -1,6 +1,25 @@
|
||||
# Clash
|
||||
<h1 align="center">
|
||||
<img src="https://github.com/Dreamacro/clash/raw/master/docs/logo.png" alt="Clash" width="200">
|
||||
<br>
|
||||
Clash
|
||||
<br>
|
||||
</h1>
|
||||
|
||||
A rule based proxy in Go.
|
||||
<h4 align="center">A rule based proxy in Go.</h4>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://travis-ci.org/Dreamacro/clash">
|
||||
<img src="https://img.shields.io/travis/Dreamacro/clash.svg?style=flat-square"
|
||||
alt="Travis-CI">
|
||||
</a>
|
||||
<a href="https://goreportcard.com/report/github.com/Dreamacro/clash">
|
||||
<img src="https://goreportcard.com/badge/github.com/Dreamacro/clash?style=flat-square">
|
||||
</a>
|
||||
<a href="https://app.fossa.io/projects/git%2Bgithub.com%2FDreamacro%2Fclash?ref=badge_shield" alt="FOSSA Status"><img src="https://app.fossa.io/api/projects/git%2Bgithub.com%2FDreamacro%2Fclash.svg?type=shield"/></a>
|
||||
<a href="https://github.com/Dreamacro/clash/releases">
|
||||
<img src="https://img.shields.io/github/release/Dreamacro/clash/all.svg?style=flat-square">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## Features
|
||||
|
||||
@ -26,12 +45,16 @@ Unfortunately, there is no native elegant way to implement golang's daemon.
|
||||
|
||||
So we can use third-party daemon tools like pm2, supervisor, and so on.
|
||||
|
||||
In the case of pm2, we can start the daemon this way:
|
||||
In the case of [pm2](https://github.com/Unitech/pm2), we can start the daemon this way:
|
||||
|
||||
```sh
|
||||
pm2 start clash
|
||||
```
|
||||
|
||||
If you have Docker installed, you can run clash directly using `docker-compose`.
|
||||
|
||||
[Run clash in docker](https://github.com/Dreamacro/clash/wiki/Run-clash-in-docker)
|
||||
|
||||
## Config
|
||||
|
||||
Configuration file at `$HOME/.config/clash/config.ini`
|
||||
@ -43,11 +66,20 @@ Below is a simple demo configuration file:
|
||||
port = 7890
|
||||
socks-port = 7891
|
||||
|
||||
# A RESTful API for clash
|
||||
external-controller = 127.0.0.1:8080
|
||||
|
||||
[Proxy]
|
||||
# name = ss, server, port, cipher, password
|
||||
# The types of cipher are consistent with go-shadowsocks2
|
||||
# support AEAD_AES_128_GCM AEAD_AES_192_GCM AEAD_AES_256_GCM AEAD_CHACHA20_POLY1305 AES-128-CTR AES-192-CTR AES-256-CTR AES-128-CFB AES-192-CFB AES-256-CFB CHACHA20-IETF XCHACHA20
|
||||
Proxy = ss, server, port, AEAD_CHACHA20_POLY1305, password
|
||||
Proxy1 = ss, server1, port, AEAD_CHACHA20_POLY1305, password
|
||||
Proxy2 = ss, server2, port, AEAD_CHACHA20_POLY1305, password
|
||||
|
||||
[Proxy Group]
|
||||
# url-test select which proxy will be used by benchmarking speed to a URL.
|
||||
# name = url-test, [proxys], url, interval(second)
|
||||
Proxy = url-test, Proxy1, Proxy2, http://www.google.com/generate_204, 300
|
||||
|
||||
[Rule]
|
||||
DOMAIN-SUFFIX,google.com,Proxy
|
||||
@ -57,6 +89,9 @@ GEOIP,CN,DIRECT
|
||||
FINAL,,Proxy # note: there is two ","
|
||||
```
|
||||
|
||||
## License
|
||||
[](https://app.fossa.io/projects/git%2Bgithub.com%2FDreamacro%2Fclash?ref=badge_large)
|
||||
|
||||
## TODO
|
||||
|
||||
- [ ] Complementing the necessary rule operators
|
||||
|
@ -22,12 +22,21 @@ func (d *DirectAdapter) Close() {
|
||||
d.conn.Close()
|
||||
}
|
||||
|
||||
// Close is used to close connection
|
||||
// Conn is used to http request
|
||||
func (d *DirectAdapter) Conn() net.Conn {
|
||||
return d.conn
|
||||
}
|
||||
|
||||
type Direct struct {
|
||||
traffic *C.Traffic
|
||||
}
|
||||
|
||||
func (d *Direct) Name() string {
|
||||
return "Direct"
|
||||
}
|
||||
|
||||
func (d *Direct) Type() C.AdapterType {
|
||||
return C.Direct
|
||||
}
|
||||
|
||||
func (d *Direct) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err error) {
|
||||
@ -36,9 +45,9 @@ func (d *Direct) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err error) {
|
||||
return
|
||||
}
|
||||
c.(*net.TCPConn).SetKeepAlive(true)
|
||||
return &DirectAdapter{conn: c}, nil
|
||||
return &DirectAdapter{conn: NewTrafficTrack(c, d.traffic)}, nil
|
||||
}
|
||||
|
||||
func NewDirect() *Direct {
|
||||
return &Direct{}
|
||||
func NewDirect(traffic *C.Traffic) *Direct {
|
||||
return &Direct{traffic: traffic}
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ func (r *RejectAdapter) ReadWriter() io.ReadWriter {
|
||||
// Close is used to close connection
|
||||
func (r *RejectAdapter) Close() {}
|
||||
|
||||
// Close is used to close connection
|
||||
// Conn is used to http request
|
||||
func (r *RejectAdapter) Conn() net.Conn {
|
||||
return nil
|
||||
}
|
||||
@ -27,6 +27,14 @@ func (r *RejectAdapter) Conn() net.Conn {
|
||||
type Reject struct {
|
||||
}
|
||||
|
||||
func (r *Reject) Name() string {
|
||||
return "Reject"
|
||||
}
|
||||
|
||||
func (r *Reject) Type() C.AdapterType {
|
||||
return C.Reject
|
||||
}
|
||||
|
||||
func (r *Reject) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err error) {
|
||||
return &RejectAdapter{}, nil
|
||||
}
|
||||
|
65
adapters/selector.go
Normal file
65
adapters/selector.go
Normal file
@ -0,0 +1,65 @@
|
||||
package adapters
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
|
||||
type Selector struct {
|
||||
name string
|
||||
selected C.Proxy
|
||||
proxys map[string]C.Proxy
|
||||
}
|
||||
|
||||
func (s *Selector) Name() string {
|
||||
return s.name
|
||||
}
|
||||
|
||||
func (s *Selector) Type() C.AdapterType {
|
||||
return C.Selector
|
||||
}
|
||||
|
||||
func (s *Selector) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err error) {
|
||||
return s.selected.Generator(addr)
|
||||
}
|
||||
|
||||
func (s *Selector) Now() string {
|
||||
return s.selected.Name()
|
||||
}
|
||||
|
||||
func (s *Selector) All() []string {
|
||||
var all []string
|
||||
for k, _ := range s.proxys {
|
||||
all = append(all, k)
|
||||
}
|
||||
return all
|
||||
}
|
||||
|
||||
func (s *Selector) Set(name string) error {
|
||||
proxy, exist := s.proxys[name]
|
||||
if !exist {
|
||||
return errors.New("Proxy does not exist")
|
||||
}
|
||||
s.selected = proxy
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewSelector(name string, proxys map[string]C.Proxy) (*Selector, error) {
|
||||
if len(proxys) == 0 {
|
||||
return nil, errors.New("Provide at least one proxy")
|
||||
}
|
||||
|
||||
mapping := make(map[string]C.Proxy)
|
||||
var init string
|
||||
for k, v := range proxys {
|
||||
mapping[k] = v
|
||||
init = k
|
||||
}
|
||||
s := &Selector{
|
||||
name: name,
|
||||
proxys: mapping,
|
||||
selected: proxys[init],
|
||||
}
|
||||
return s, nil
|
||||
}
|
@ -34,31 +34,44 @@ func (ss *ShadowsocksAdapter) Conn() net.Conn {
|
||||
}
|
||||
|
||||
type ShadowSocks struct {
|
||||
server string
|
||||
cipher string
|
||||
password string
|
||||
server string
|
||||
name string
|
||||
cipher core.Cipher
|
||||
traffic *C.Traffic
|
||||
}
|
||||
|
||||
func (ss *ShadowSocks) Name() string {
|
||||
return ss.name
|
||||
}
|
||||
|
||||
func (ss *ShadowSocks) Type() C.AdapterType {
|
||||
return C.Shadowsocks
|
||||
}
|
||||
|
||||
func (ss *ShadowSocks) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err error) {
|
||||
var key []byte
|
||||
ciph, _ := core.PickCipher(ss.cipher, key, ss.password)
|
||||
c, err := net.Dial("tcp", ss.server)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error", ss.server)
|
||||
}
|
||||
c.(*net.TCPConn).SetKeepAlive(true)
|
||||
c = ciph.StreamConn(c)
|
||||
c = ss.cipher.StreamConn(c)
|
||||
_, err = c.Write(serializesSocksAddr(addr))
|
||||
return &ShadowsocksAdapter{conn: c}, err
|
||||
return &ShadowsocksAdapter{conn: NewTrafficTrack(c, ss.traffic)}, err
|
||||
}
|
||||
|
||||
func NewShadowSocks(ssURL string) *ShadowSocks {
|
||||
func NewShadowSocks(name string, ssURL string, traffic *C.Traffic) (*ShadowSocks, error) {
|
||||
var key []byte
|
||||
server, cipher, password, _ := parseURL(ssURL)
|
||||
return &ShadowSocks{
|
||||
server: server,
|
||||
cipher: cipher,
|
||||
password: password,
|
||||
ciph, err := core.PickCipher(cipher, key, password)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ss %s initialize error: %s", server, err.Error())
|
||||
}
|
||||
return &ShadowSocks{
|
||||
server: server,
|
||||
name: name,
|
||||
cipher: ciph,
|
||||
traffic: traffic,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseURL(s string) (addr, cipher, password string, err error) {
|
||||
@ -84,13 +97,13 @@ func serializesSocksAddr(addr *C.Addr) []byte {
|
||||
case socks.AtypDomainName:
|
||||
len := uint8(len(addr.Host))
|
||||
host := []byte(addr.Host)
|
||||
buf = [][]byte{[]byte{aType, len}, host, port}
|
||||
buf = [][]byte{{aType, len}, host, port}
|
||||
case socks.AtypIPv4:
|
||||
host := addr.IP.To4()
|
||||
buf = [][]byte{[]byte{aType}, host, port}
|
||||
buf = [][]byte{{aType}, host, port}
|
||||
case socks.AtypIPv6:
|
||||
host := addr.IP.To16()
|
||||
buf = [][]byte{[]byte{aType}, host, port}
|
||||
buf = [][]byte{{aType}, host, port}
|
||||
}
|
||||
return bytes.Join(buf, []byte(""))
|
||||
}
|
||||
|
168
adapters/urltest.go
Normal file
168
adapters/urltest.go
Normal file
@ -0,0 +1,168 @@
|
||||
package adapters
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
|
||||
type URLTest struct {
|
||||
name string
|
||||
proxys []C.Proxy
|
||||
url *url.URL
|
||||
rawURL string
|
||||
addr *C.Addr
|
||||
fast C.Proxy
|
||||
delay time.Duration
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func (u *URLTest) Name() string {
|
||||
return u.name
|
||||
}
|
||||
|
||||
func (u *URLTest) Type() C.AdapterType {
|
||||
return C.URLTest
|
||||
}
|
||||
|
||||
func (u *URLTest) Now() string {
|
||||
return u.fast.Name()
|
||||
}
|
||||
|
||||
func (u *URLTest) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err error) {
|
||||
return u.fast.Generator(addr)
|
||||
}
|
||||
|
||||
func (u *URLTest) Close() {
|
||||
u.done <- struct{}{}
|
||||
}
|
||||
|
||||
func (u *URLTest) loop() {
|
||||
tick := time.NewTicker(u.delay)
|
||||
go u.speedTest()
|
||||
Loop:
|
||||
for {
|
||||
select {
|
||||
case <-tick.C:
|
||||
go u.speedTest()
|
||||
case <-u.done:
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (u *URLTest) speedTest() {
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(len(u.proxys))
|
||||
c := make(chan interface{})
|
||||
fast := selectFast(c)
|
||||
timer := time.NewTimer(u.delay)
|
||||
|
||||
for _, p := range u.proxys {
|
||||
go func(p C.Proxy) {
|
||||
err := getUrl(p, u.addr, u.rawURL)
|
||||
if err == nil {
|
||||
c <- p
|
||||
}
|
||||
wg.Done()
|
||||
}(p)
|
||||
}
|
||||
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(c)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-timer.C:
|
||||
// Wait for fast to return or close.
|
||||
<-fast
|
||||
case p, open := <-fast:
|
||||
if open {
|
||||
u.fast = p.(C.Proxy)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getUrl(proxy C.Proxy, addr *C.Addr, rawURL string) (err error) {
|
||||
instance, err := proxy.Generator(addr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer instance.Close()
|
||||
transport := &http.Transport{
|
||||
Dial: func(string, string) (net.Conn, error) {
|
||||
return instance.Conn(), nil
|
||||
},
|
||||
// from http.DefaultTransport
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
}
|
||||
client := http.Client{Transport: transport}
|
||||
req, err := client.Get(rawURL)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
req.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func selectFast(in chan interface{}) chan interface{} {
|
||||
out := make(chan interface{})
|
||||
go func() {
|
||||
p, open := <-in
|
||||
if open {
|
||||
out <- p
|
||||
}
|
||||
close(out)
|
||||
for range in {
|
||||
}
|
||||
}()
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func NewURLTest(name string, proxys []C.Proxy, rawURL string, delay time.Duration) (*URLTest, error) {
|
||||
u, err := url.Parse(rawURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
port := u.Port()
|
||||
if port == "" {
|
||||
if u.Scheme == "https" {
|
||||
port = "443"
|
||||
} else if u.Scheme == "http" {
|
||||
port = "80"
|
||||
} else {
|
||||
return nil, fmt.Errorf("%s scheme not Support", rawURL)
|
||||
}
|
||||
}
|
||||
|
||||
addr := &C.Addr{
|
||||
AddrType: C.AtypDomainName,
|
||||
Host: u.Hostname(),
|
||||
IP: nil,
|
||||
Port: port,
|
||||
}
|
||||
|
||||
urlTest := &URLTest{
|
||||
name: name,
|
||||
proxys: proxys[:],
|
||||
rawURL: rawURL,
|
||||
url: u,
|
||||
addr: addr,
|
||||
fast: proxys[0],
|
||||
delay: delay,
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
go urlTest.loop()
|
||||
return urlTest, nil
|
||||
}
|
28
adapters/util.go
Normal file
28
adapters/util.go
Normal file
@ -0,0 +1,28 @@
|
||||
package adapters
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
|
||||
type TrafficTrack struct {
|
||||
net.Conn
|
||||
traffic *C.Traffic
|
||||
}
|
||||
|
||||
func (tt *TrafficTrack) Read(b []byte) (int, error) {
|
||||
n, err := tt.Conn.Read(b)
|
||||
tt.traffic.Down() <- int64(n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (tt *TrafficTrack) Write(b []byte) (int, error) {
|
||||
n, err := tt.Conn.Write(b)
|
||||
tt.traffic.Up() <- int64(n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func NewTrafficTrack(conn net.Conn, traffic *C.Traffic) *TrafficTrack {
|
||||
return &TrafficTrack{traffic: traffic, Conn: conn}
|
||||
}
|
@ -5,6 +5,15 @@ import (
|
||||
"net"
|
||||
)
|
||||
|
||||
// Adapter Type
|
||||
const (
|
||||
Direct AdapterType = iota
|
||||
Reject
|
||||
Selector
|
||||
Shadowsocks
|
||||
URLTest
|
||||
)
|
||||
|
||||
type ProxyAdapter interface {
|
||||
ReadWriter() io.ReadWriter
|
||||
Conn() net.Conn
|
||||
@ -18,5 +27,27 @@ type ServerAdapter interface {
|
||||
}
|
||||
|
||||
type Proxy interface {
|
||||
Name() string
|
||||
Type() AdapterType
|
||||
Generator(addr *Addr) (ProxyAdapter, error)
|
||||
}
|
||||
|
||||
// AdapterType is enum of adapter type
|
||||
type AdapterType int
|
||||
|
||||
func (at AdapterType) String() string {
|
||||
switch at {
|
||||
case Direct:
|
||||
return "Direct"
|
||||
case Reject:
|
||||
return "Reject"
|
||||
case Selector:
|
||||
return "Selector"
|
||||
case Shadowsocks:
|
||||
return "Shadowsocks"
|
||||
case URLTest:
|
||||
return "URLTest"
|
||||
default:
|
||||
return "Unknow"
|
||||
}
|
||||
}
|
||||
|
@ -16,8 +16,8 @@ import (
|
||||
|
||||
const (
|
||||
Name = "clash"
|
||||
DefalutHTTPPort = "7890"
|
||||
DefalutSOCKSPort = "7891"
|
||||
DefalutHTTPPort = 7890
|
||||
DefalutSOCKSPort = 7891
|
||||
)
|
||||
|
||||
var (
|
||||
@ -26,12 +26,24 @@ var (
|
||||
MMDBPath string
|
||||
)
|
||||
|
||||
type General struct {
|
||||
Mode *string `json:"mode,omitempty"`
|
||||
AllowLan *bool `json:"allow-lan,omitempty"`
|
||||
Port *int `json:"port,omitempty"`
|
||||
SocksPort *int `json:"socks-port,omitempty"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
currentUser, err := user.Current()
|
||||
if err != nil {
|
||||
log.Fatalf("Can't get current user: %s", err.Error())
|
||||
dir := os.Getenv("HOME")
|
||||
if dir == "" {
|
||||
log.Fatalf("Can't get current user: %s", err.Error())
|
||||
}
|
||||
HomeDir = dir
|
||||
} else {
|
||||
HomeDir = currentUser.HomeDir
|
||||
}
|
||||
HomeDir = currentUser.HomeDir
|
||||
|
||||
dirPath := path.Join(HomeDir, ".config", Name)
|
||||
if _, err := os.Stat(dirPath); os.IsNotExist(err) {
|
||||
|
7
constant/proxy.go
Normal file
7
constant/proxy.go
Normal file
@ -0,0 +1,7 @@
|
||||
package constant
|
||||
|
||||
// ProxySignal is used to handle graceful shutdown of proxy
|
||||
type ProxySignal struct {
|
||||
Done chan<- struct{}
|
||||
Closed <-chan struct{}
|
||||
}
|
@ -11,8 +11,26 @@ const (
|
||||
|
||||
type RuleType int
|
||||
|
||||
func (rt RuleType) String() string {
|
||||
switch rt {
|
||||
case DomainSuffix:
|
||||
return "DomainSuffix"
|
||||
case DomainKeyword:
|
||||
return "DomainKeyword"
|
||||
case GEOIP:
|
||||
return "GEOIP"
|
||||
case IPCIDR:
|
||||
return "IPCIDR"
|
||||
case FINAL:
|
||||
return "FINAL"
|
||||
default:
|
||||
return "Unknow"
|
||||
}
|
||||
}
|
||||
|
||||
type Rule interface {
|
||||
RuleType() RuleType
|
||||
IsMatch(addr *Addr) bool
|
||||
Adapter() string
|
||||
Payload() string
|
||||
}
|
||||
|
55
constant/traffic.go
Normal file
55
constant/traffic.go
Normal file
@ -0,0 +1,55 @@
|
||||
package constant
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type Traffic struct {
|
||||
up chan int64
|
||||
down chan int64
|
||||
upCount int64
|
||||
downCount int64
|
||||
upTotal int64
|
||||
downTotal int64
|
||||
interval time.Duration
|
||||
}
|
||||
|
||||
func (t *Traffic) Up() chan<- int64 {
|
||||
return t.up
|
||||
}
|
||||
|
||||
func (t *Traffic) Down() chan<- int64 {
|
||||
return t.down
|
||||
}
|
||||
|
||||
func (t *Traffic) Now() (up int64, down int64) {
|
||||
return t.upTotal, t.downTotal
|
||||
}
|
||||
|
||||
func (t *Traffic) handle() {
|
||||
go t.handleCh(t.up, &t.upCount, &t.upTotal)
|
||||
go t.handleCh(t.down, &t.downCount, &t.downTotal)
|
||||
}
|
||||
|
||||
func (t *Traffic) handleCh(ch <-chan int64, count *int64, total *int64) {
|
||||
ticker := time.NewTicker(t.interval)
|
||||
for {
|
||||
select {
|
||||
case n := <-ch:
|
||||
*count += n
|
||||
case <-ticker.C:
|
||||
*total = *count
|
||||
*count = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func NewTraffic(interval time.Duration) *Traffic {
|
||||
t := &Traffic{
|
||||
up: make(chan int64),
|
||||
down: make(chan int64),
|
||||
interval: interval,
|
||||
}
|
||||
go t.handle()
|
||||
return t
|
||||
}
|
BIN
docs/logo.png
Executable file
BIN
docs/logo.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
31
hub/common.go
Normal file
31
hub/common.go
Normal file
@ -0,0 +1,31 @@
|
||||
package hub
|
||||
|
||||
import (
|
||||
"github.com/Dreamacro/clash/proxy"
|
||||
T "github.com/Dreamacro/clash/tunnel"
|
||||
)
|
||||
|
||||
var (
|
||||
tunnel = T.GetInstance()
|
||||
listener = proxy.Instance()
|
||||
)
|
||||
|
||||
type Error struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
type Errors struct {
|
||||
Errors map[string]string `json:"errors"`
|
||||
}
|
||||
|
||||
func formatErrors(errorsMap map[string]error) (bool, Errors) {
|
||||
errors := make(map[string]string)
|
||||
hasError := false
|
||||
for key, err := range errorsMap {
|
||||
if err != nil {
|
||||
errors[key] = err.Error()
|
||||
hasError = true
|
||||
}
|
||||
}
|
||||
return hasError, Errors{Errors: errors}
|
||||
}
|
74
hub/configs.go
Normal file
74
hub/configs.go
Normal file
@ -0,0 +1,74 @@
|
||||
package hub
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/proxy"
|
||||
T "github.com/Dreamacro/clash/tunnel"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/render"
|
||||
)
|
||||
|
||||
func configRouter() http.Handler {
|
||||
r := chi.NewRouter()
|
||||
r.Get("/", getConfigs)
|
||||
r.Put("/", updateConfigs)
|
||||
return r
|
||||
}
|
||||
|
||||
var modeMapping = map[string]T.Mode{
|
||||
"Global": T.Global,
|
||||
"Rule": T.Rule,
|
||||
"Direct": T.Direct,
|
||||
}
|
||||
|
||||
func getConfigs(w http.ResponseWriter, r *http.Request) {
|
||||
info := listener.Info()
|
||||
mode := tunnel.GetMode().String()
|
||||
info.Mode = &mode
|
||||
render.JSON(w, r, info)
|
||||
}
|
||||
|
||||
func updateConfigs(w http.ResponseWriter, r *http.Request) {
|
||||
general := &C.General{}
|
||||
err := render.DecodeJSON(r.Body, general)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
render.JSON(w, r, Error{
|
||||
Error: "Format error",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// update errors
|
||||
var proxyErr, modeErr error
|
||||
|
||||
// update proxy
|
||||
listener := proxy.Instance()
|
||||
proxyErr = listener.Update(general.AllowLan, general.Port, general.SocksPort)
|
||||
|
||||
// update mode
|
||||
if general.Mode != nil {
|
||||
mode, ok := modeMapping[*general.Mode]
|
||||
if !ok {
|
||||
modeErr = fmt.Errorf("Mode error")
|
||||
} else {
|
||||
tunnel.SetMode(mode)
|
||||
}
|
||||
}
|
||||
|
||||
hasError, errors := formatErrors(map[string]error{
|
||||
"proxy": proxyErr,
|
||||
"mode": modeErr,
|
||||
})
|
||||
|
||||
if hasError {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
render.JSON(w, r, errors)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
129
hub/proxys.go
Normal file
129
hub/proxys.go
Normal file
@ -0,0 +1,129 @@
|
||||
package hub
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
A "github.com/Dreamacro/clash/adapters"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/render"
|
||||
)
|
||||
|
||||
func proxyRouter() http.Handler {
|
||||
r := chi.NewRouter()
|
||||
r.Get("/", getProxys)
|
||||
r.Get("/{name}", getProxy)
|
||||
r.Put("/{name}", updateProxy)
|
||||
return r
|
||||
}
|
||||
|
||||
type SampleProxy struct {
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
type Selector struct {
|
||||
Type string `json:"type"`
|
||||
Now string `json:"now"`
|
||||
All []string `json:"all"`
|
||||
}
|
||||
|
||||
type URLTest struct {
|
||||
Type string `json:"type"`
|
||||
Now string `json:"now"`
|
||||
}
|
||||
|
||||
func transformProxy(proxy C.Proxy) interface{} {
|
||||
t := proxy.Type()
|
||||
switch t {
|
||||
case C.Selector:
|
||||
selector := proxy.(*A.Selector)
|
||||
return Selector{
|
||||
Type: t.String(),
|
||||
Now: selector.Now(),
|
||||
All: selector.All(),
|
||||
}
|
||||
case C.URLTest:
|
||||
return URLTest{
|
||||
Type: t.String(),
|
||||
Now: proxy.(*A.URLTest).Now(),
|
||||
}
|
||||
default:
|
||||
return SampleProxy{
|
||||
Type: proxy.Type().String(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type GetProxysResponse struct {
|
||||
Proxys map[string]interface{} `json:"proxys"`
|
||||
}
|
||||
|
||||
func getProxys(w http.ResponseWriter, r *http.Request) {
|
||||
_, rawProxys := tunnel.Config()
|
||||
proxys := make(map[string]interface{})
|
||||
for name, proxy := range rawProxys {
|
||||
proxys[name] = transformProxy(proxy)
|
||||
}
|
||||
render.JSON(w, r, GetProxysResponse{Proxys: proxys})
|
||||
}
|
||||
|
||||
func getProxy(w http.ResponseWriter, r *http.Request) {
|
||||
name := chi.URLParam(r, "name")
|
||||
_, proxys := tunnel.Config()
|
||||
proxy, exist := proxys[name]
|
||||
if !exist {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
render.JSON(w, r, Error{
|
||||
Error: "Proxy not found",
|
||||
})
|
||||
return
|
||||
}
|
||||
render.JSON(w, r, transformProxy(proxy))
|
||||
}
|
||||
|
||||
type UpdateProxyRequest struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func updateProxy(w http.ResponseWriter, r *http.Request) {
|
||||
req := UpdateProxyRequest{}
|
||||
if err := render.DecodeJSON(r.Body, &req); err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
render.JSON(w, r, Error{
|
||||
Error: "Format error",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
name := chi.URLParam(r, "name")
|
||||
_, proxys := tunnel.Config()
|
||||
proxy, exist := proxys[name]
|
||||
if !exist {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
render.JSON(w, r, Error{
|
||||
Error: "Proxy not found",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
selector, ok := proxy.(*A.Selector)
|
||||
if !ok {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
render.JSON(w, r, Error{
|
||||
Error: "Proxy can't update",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if err := selector.Set(req.Name); err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
render.JSON(w, r, Error{
|
||||
Error: fmt.Sprintf("Selector update error: %s", err.Error()),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
53
hub/rules.go
Normal file
53
hub/rules.go
Normal file
@ -0,0 +1,53 @@
|
||||
package hub
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/render"
|
||||
)
|
||||
|
||||
func ruleRouter() http.Handler {
|
||||
r := chi.NewRouter()
|
||||
r.Get("/", getRules)
|
||||
r.Put("/", updateRules)
|
||||
return r
|
||||
}
|
||||
|
||||
type Rule struct {
|
||||
Name string `json:"name"`
|
||||
Payload string `json:"type"`
|
||||
}
|
||||
|
||||
type GetRulesResponse struct {
|
||||
Rules []Rule `json:"rules"`
|
||||
}
|
||||
|
||||
func getRules(w http.ResponseWriter, r *http.Request) {
|
||||
rulesCfg, _ := tunnel.Config()
|
||||
|
||||
var rules []Rule
|
||||
for _, rule := range rulesCfg {
|
||||
rules = append(rules, Rule{
|
||||
Name: rule.RuleType().String(),
|
||||
Payload: rule.Payload(),
|
||||
})
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
render.JSON(w, r, GetRulesResponse{
|
||||
Rules: rules,
|
||||
})
|
||||
}
|
||||
|
||||
func updateRules(w http.ResponseWriter, r *http.Request) {
|
||||
err := tunnel.UpdateConfig()
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
render.JSON(w, r, Error{
|
||||
Error: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
109
hub/server.go
Normal file
109
hub/server.go
Normal file
@ -0,0 +1,109 @@
|
||||
package hub
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
T "github.com/Dreamacro/clash/tunnel"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/render"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Traffic struct {
|
||||
Up int64 `json:"up"`
|
||||
Down int64 `json:"down"`
|
||||
}
|
||||
|
||||
func NewHub(addr string) {
|
||||
r := chi.NewRouter()
|
||||
|
||||
r.Get("/traffic", traffic)
|
||||
r.Get("/logs", getLogs)
|
||||
r.Mount("/configs", configRouter())
|
||||
r.Mount("/proxys", proxyRouter())
|
||||
r.Mount("/rules", ruleRouter())
|
||||
|
||||
err := http.ListenAndServe(addr, r)
|
||||
if err != nil {
|
||||
log.Fatalf("External controller error: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func traffic(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
tick := time.NewTicker(time.Second)
|
||||
t := tunnel.Traffic()
|
||||
for range tick.C {
|
||||
up, down := t.Now()
|
||||
if err := json.NewEncoder(w).Encode(Traffic{
|
||||
Up: up,
|
||||
Down: down,
|
||||
}); err != nil {
|
||||
break
|
||||
}
|
||||
w.(http.Flusher).Flush()
|
||||
}
|
||||
}
|
||||
|
||||
type GetLogs struct {
|
||||
Level string `json:"level"`
|
||||
}
|
||||
|
||||
type Log struct {
|
||||
Type string `json:"type"`
|
||||
Payload string `json:"payload"`
|
||||
}
|
||||
|
||||
func getLogs(w http.ResponseWriter, r *http.Request) {
|
||||
req := &GetLogs{}
|
||||
render.DecodeJSON(r.Body, req)
|
||||
if req.Level == "" {
|
||||
req.Level = "info"
|
||||
}
|
||||
|
||||
mapping := map[string]T.LogLevel{
|
||||
"info": T.INFO,
|
||||
"debug": T.DEBUG,
|
||||
"error": T.ERROR,
|
||||
"warning": T.WARNING,
|
||||
}
|
||||
|
||||
level, ok := mapping[req.Level]
|
||||
if !ok {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
render.JSON(w, r, Error{
|
||||
Error: "Level error",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
src := tunnel.Log()
|
||||
sub, err := src.Subscribe()
|
||||
defer src.UnSubscribe(sub)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
render.JSON(w, r, Error{
|
||||
Error: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
render.Status(r, http.StatusOK)
|
||||
for elm := range sub {
|
||||
log := elm.(T.Log)
|
||||
if log.LogLevel > level {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(Log{
|
||||
Type: log.Type(),
|
||||
Payload: log.Payload,
|
||||
}); err != nil {
|
||||
break
|
||||
}
|
||||
w.(http.Flusher).Flush()
|
||||
}
|
||||
}
|
30
main.go
30
main.go
@ -6,37 +6,33 @@ import (
|
||||
"syscall"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/proxy/http"
|
||||
"github.com/Dreamacro/clash/proxy/socks"
|
||||
"github.com/Dreamacro/clash/hub"
|
||||
"github.com/Dreamacro/clash/proxy"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if err := tunnel.GetInstance().UpdateConfig(); err != nil {
|
||||
log.Fatalf("Parse config error: %s", err.Error())
|
||||
}
|
||||
|
||||
if err := proxy.Instance().Run(); err != nil {
|
||||
log.Fatalf("Proxy listen error: %s", err.Error())
|
||||
}
|
||||
|
||||
// Hub
|
||||
cfg, err := C.GetConfig()
|
||||
if err != nil {
|
||||
log.Fatalf("Read config error: %s", err.Error())
|
||||
}
|
||||
|
||||
port, socksPort := C.DefalutHTTPPort, C.DefalutSOCKSPort
|
||||
section := cfg.Section("General")
|
||||
if key, err := section.GetKey("port"); err == nil {
|
||||
port = key.Value()
|
||||
if key, err := section.GetKey("external-controller"); err == nil {
|
||||
go hub.NewHub(key.Value())
|
||||
}
|
||||
|
||||
if key, err := section.GetKey("socks-port"); err == nil {
|
||||
socksPort = key.Value()
|
||||
}
|
||||
|
||||
err = tunnel.GetInstance().UpdateConfig()
|
||||
if err != nil {
|
||||
log.Fatalf("Parse config error: %s", err.Error())
|
||||
}
|
||||
|
||||
go http.NewHttpProxy(port)
|
||||
go socks.NewSocksProxy(socksPort)
|
||||
|
||||
sigCh := make(chan os.Signal, 1)
|
||||
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-sigCh
|
||||
|
@ -1,18 +1,3 @@
|
||||
package observable
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
type Iterable <-chan interface{}
|
||||
|
||||
func NewIterable(any interface{}) (Iterable, error) {
|
||||
switch any := any.(type) {
|
||||
case chan interface{}:
|
||||
return Iterable(any), nil
|
||||
case <-chan interface{}:
|
||||
return Iterable(any), nil
|
||||
default:
|
||||
return nil, errors.New("type error")
|
||||
}
|
||||
}
|
||||
|
@ -50,7 +50,6 @@ func (o *Observable) Subscribe() (Subscription, error) {
|
||||
func (o *Observable) UnSubscribe(sub Subscription) {
|
||||
elm, exist := o.listener.Load(sub)
|
||||
if !exist {
|
||||
println("not exist")
|
||||
return
|
||||
}
|
||||
subscriber := elm.(*Subscriber)
|
||||
|
@ -27,11 +27,7 @@ func TestObservable(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
count := 0
|
||||
for {
|
||||
_, open := <-data
|
||||
if !open {
|
||||
break
|
||||
}
|
||||
for range data {
|
||||
count = count + 1
|
||||
}
|
||||
if count != 5 {
|
||||
@ -49,11 +45,7 @@ func TestObservable_MutilSubscribe(t *testing.T) {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
waitCh := func(ch <-chan interface{}) {
|
||||
for {
|
||||
_, open := <-ch
|
||||
if !open {
|
||||
break
|
||||
}
|
||||
for range ch {
|
||||
count = count + 1
|
||||
}
|
||||
wg.Done()
|
||||
@ -80,6 +72,25 @@ func TestObservable_UnSubscribe(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestObservable_SubscribeClosedSource(t *testing.T) {
|
||||
iter := iterator([]interface{}{1})
|
||||
src := NewObservable(iter)
|
||||
data, _ := src.Subscribe()
|
||||
<-data
|
||||
|
||||
_, closed := src.Subscribe()
|
||||
if closed == nil {
|
||||
t.Error("Observable should be closed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestObservable_UnSubscribeWithNotExistSubscription(t *testing.T) {
|
||||
sub := Subscription(make(chan interface{}))
|
||||
iter := iterator([]interface{}{1})
|
||||
src := NewObservable(iter)
|
||||
src.UnSubscribe(sub)
|
||||
}
|
||||
|
||||
func TestObservable_SubscribeGoroutineLeak(t *testing.T) {
|
||||
// waiting for other goroutine recycle
|
||||
time.Sleep(120 * time.Millisecond)
|
||||
@ -97,11 +108,7 @@ func TestObservable_SubscribeGoroutineLeak(t *testing.T) {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(max)
|
||||
waitCh := func(ch <-chan interface{}) {
|
||||
for {
|
||||
_, open := <-ch
|
||||
if !open {
|
||||
break
|
||||
}
|
||||
for range ch {
|
||||
}
|
||||
wg.Done()
|
||||
}
|
||||
|
@ -1,15 +0,0 @@
|
||||
package observable
|
||||
|
||||
func mergeWithBytes(ch <-chan interface{}, buf []byte) chan interface{} {
|
||||
out := make(chan interface{})
|
||||
go func() {
|
||||
defer close(out)
|
||||
if len(buf) != 0 {
|
||||
out <- buf
|
||||
}
|
||||
for elm := range ch {
|
||||
out <- elm
|
||||
}
|
||||
}()
|
||||
return out
|
||||
}
|
@ -32,7 +32,6 @@ func (h *HttpAdapter) Connect(proxy C.ProxyAdapter) {
|
||||
// from http.DefaultTransport
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
}
|
||||
resp, err := req.RoundTrip(h.r)
|
||||
@ -48,7 +47,23 @@ func (h *HttpAdapter) Connect(proxy C.ProxyAdapter) {
|
||||
}
|
||||
}
|
||||
h.w.WriteHeader(resp.StatusCode)
|
||||
io.Copy(h.w, resp.Body)
|
||||
var writer io.Writer = h.w
|
||||
if len(resp.TransferEncoding) > 0 && resp.TransferEncoding[0] == "chunked" {
|
||||
writer = ChunkWriter{Writer: h.w}
|
||||
}
|
||||
io.Copy(writer, resp.Body)
|
||||
}
|
||||
|
||||
type ChunkWriter struct {
|
||||
io.Writer
|
||||
}
|
||||
|
||||
func (cw ChunkWriter) Write(b []byte) (int, error) {
|
||||
n, err := cw.Writer.Write(b)
|
||||
if err == nil {
|
||||
cw.Writer.(http.Flusher).Flush()
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func NewHttp(host string, w http.ResponseWriter, r *http.Request) (*HttpAdapter, chan struct{}) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
@ -17,9 +17,20 @@ var (
|
||||
tun = tunnel.GetInstance()
|
||||
)
|
||||
|
||||
func NewHttpProxy(port string) {
|
||||
func NewHttpProxy(addr string) (*C.ProxySignal, error) {
|
||||
l, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
closed := make(chan struct{})
|
||||
signal := &C.ProxySignal{
|
||||
Done: done,
|
||||
Closed: closed,
|
||||
}
|
||||
|
||||
server := &http.Server{
|
||||
Addr: fmt.Sprintf(":%s", port),
|
||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodConnect {
|
||||
handleTunneling(w, r)
|
||||
@ -28,8 +39,20 @@ func NewHttpProxy(port string) {
|
||||
}
|
||||
}),
|
||||
}
|
||||
log.Infof("HTTP proxy :%s", port)
|
||||
server.ListenAndServe()
|
||||
|
||||
go func() {
|
||||
log.Infof("HTTP proxy listening at: %s", addr)
|
||||
server.Serve(l)
|
||||
}()
|
||||
|
||||
go func() {
|
||||
<-done
|
||||
server.Shutdown(context.Background())
|
||||
l.Close()
|
||||
closed <- struct{}{}
|
||||
}()
|
||||
|
||||
return signal, nil
|
||||
}
|
||||
|
||||
func handleHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
|
143
proxy/listener.go
Normal file
143
proxy/listener.go
Normal file
@ -0,0 +1,143 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/proxy/http"
|
||||
"github.com/Dreamacro/clash/proxy/socks"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
listener *Listener
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
type Listener struct {
|
||||
httpPort int
|
||||
socksPort int
|
||||
allowLan bool
|
||||
|
||||
// signal for update
|
||||
httpSignal *C.ProxySignal
|
||||
socksSignal *C.ProxySignal
|
||||
}
|
||||
|
||||
// Info returns the proxys's current configuration
|
||||
func (l *Listener) Info() (info C.General) {
|
||||
return C.General{
|
||||
Port: &l.httpPort,
|
||||
SocksPort: &l.socksPort,
|
||||
AllowLan: &l.allowLan,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Listener) Update(allowLan *bool, httpPort *int, socksPort *int) error {
|
||||
if allowLan != nil {
|
||||
l.allowLan = *allowLan
|
||||
}
|
||||
|
||||
var socksErr, httpErr error
|
||||
if allowLan != nil || httpPort != nil {
|
||||
newHTTPPort := l.httpPort
|
||||
if httpPort != nil {
|
||||
newHTTPPort = *httpPort
|
||||
}
|
||||
httpErr = l.updateHTTP(newHTTPPort)
|
||||
}
|
||||
|
||||
if allowLan != nil || socksPort != nil {
|
||||
newSocksPort := l.socksPort
|
||||
if socksPort != nil {
|
||||
newSocksPort = *socksPort
|
||||
}
|
||||
socksErr = l.updateSocks(newSocksPort)
|
||||
}
|
||||
|
||||
if socksErr != nil && httpErr != nil {
|
||||
return fmt.Errorf("%s\n%s", socksErr.Error(), httpErr.Error())
|
||||
} else if socksErr != nil {
|
||||
return socksErr
|
||||
} else if httpErr != nil {
|
||||
return httpErr
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Listener) updateHTTP(port int) error {
|
||||
if l.httpSignal != nil {
|
||||
signal := l.httpSignal
|
||||
signal.Done <- struct{}{}
|
||||
<-signal.Closed
|
||||
l.httpSignal = nil
|
||||
}
|
||||
|
||||
signal, err := http.NewHttpProxy(l.genAddr(port))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
l.httpSignal = signal
|
||||
l.httpPort = port
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Listener) updateSocks(port int) error {
|
||||
if l.socksSignal != nil {
|
||||
signal := l.socksSignal
|
||||
signal.Done <- struct{}{}
|
||||
<-signal.Closed
|
||||
l.socksSignal = nil
|
||||
}
|
||||
|
||||
signal, err := socks.NewSocksProxy(l.genAddr(port))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
l.socksSignal = signal
|
||||
l.socksPort = port
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Listener) genAddr(port int) string {
|
||||
host := "127.0.0.1"
|
||||
if l.allowLan {
|
||||
host = ""
|
||||
}
|
||||
return fmt.Sprintf("%s:%d", host, port)
|
||||
}
|
||||
|
||||
func (l *Listener) Run() error {
|
||||
return l.Update(&l.allowLan, &l.httpPort, &l.socksPort)
|
||||
}
|
||||
|
||||
func newListener() *Listener {
|
||||
cfg, err := C.GetConfig()
|
||||
if err != nil {
|
||||
log.Fatalf("Read config error: %s", err.Error())
|
||||
}
|
||||
|
||||
general := cfg.Section("General")
|
||||
|
||||
port := general.Key("port").RangeInt(C.DefalutHTTPPort, 1, 65535)
|
||||
socksPort := general.Key("socks-port").RangeInt(C.DefalutSOCKSPort, 1, 65535)
|
||||
allowLan := general.Key("allow-lan").MustBool()
|
||||
|
||||
return &Listener{
|
||||
httpPort: port,
|
||||
socksPort: socksPort,
|
||||
allowLan: allowLan,
|
||||
}
|
||||
}
|
||||
|
||||
func Instance() *Listener {
|
||||
once.Do(func() {
|
||||
listener = newListener()
|
||||
})
|
||||
return listener
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
package socks
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
@ -17,20 +16,41 @@ var (
|
||||
tun = tunnel.GetInstance()
|
||||
)
|
||||
|
||||
func NewSocksProxy(port string) {
|
||||
l, err := net.Listen("tcp", fmt.Sprintf(":%s", port))
|
||||
defer l.Close()
|
||||
func NewSocksProxy(addr string) (*C.ProxySignal, error) {
|
||||
l, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return
|
||||
return nil, err
|
||||
}
|
||||
log.Infof("SOCKS proxy :%s", port)
|
||||
for {
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
continue
|
||||
|
||||
done := make(chan struct{})
|
||||
closed := make(chan struct{})
|
||||
signal := &C.ProxySignal{
|
||||
Done: done,
|
||||
Closed: closed,
|
||||
}
|
||||
|
||||
go func() {
|
||||
log.Infof("SOCKS proxy listening at: %s", addr)
|
||||
for {
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
if _, open := <-done; !open {
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
go handleSocks(c)
|
||||
}
|
||||
go handleSocks(c)
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
<-done
|
||||
close(done)
|
||||
l.Close()
|
||||
closed <- struct{}{}
|
||||
}()
|
||||
|
||||
return signal, nil
|
||||
}
|
||||
|
||||
func handleSocks(conn net.Conn) {
|
||||
|
@ -27,6 +27,10 @@ func (dk *DomainKeyword) Adapter() string {
|
||||
return dk.adapter
|
||||
}
|
||||
|
||||
func (dk *DomainKeyword) Payload() string {
|
||||
return dk.keyword
|
||||
}
|
||||
|
||||
func NewDomainKeyword(keyword string, adapter string) *DomainKeyword {
|
||||
return &DomainKeyword{
|
||||
keyword: keyword,
|
||||
|
@ -27,6 +27,10 @@ func (ds *DomainSuffix) Adapter() string {
|
||||
return ds.adapter
|
||||
}
|
||||
|
||||
func (ds *DomainSuffix) Payload() string {
|
||||
return ds.suffix
|
||||
}
|
||||
|
||||
func NewDomainSuffix(suffix string, adapter string) *DomainSuffix {
|
||||
return &DomainSuffix{
|
||||
suffix: suffix,
|
||||
|
@ -20,6 +20,10 @@ func (f *Final) Adapter() string {
|
||||
return f.adapter
|
||||
}
|
||||
|
||||
func (f *Final) Payload() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func NewFinal(adapter string) *Final {
|
||||
return &Final{
|
||||
adapter: adapter,
|
||||
|
@ -38,6 +38,10 @@ func (g *GEOIP) Adapter() string {
|
||||
return g.adapter
|
||||
}
|
||||
|
||||
func (g *GEOIP) Payload() string {
|
||||
return g.country
|
||||
}
|
||||
|
||||
func NewGEOIP(country string, adapter string) *GEOIP {
|
||||
return &GEOIP{
|
||||
country: country,
|
||||
|
@ -23,8 +23,12 @@ func (i *IPCIDR) IsMatch(addr *C.Addr) bool {
|
||||
return i.ipnet.Contains(*addr.IP)
|
||||
}
|
||||
|
||||
func (g *IPCIDR) Adapter() string {
|
||||
return g.adapter
|
||||
func (i *IPCIDR) Adapter() string {
|
||||
return i.adapter
|
||||
}
|
||||
|
||||
func (i *IPCIDR) Payload() string {
|
||||
return i.ipnet.String()
|
||||
}
|
||||
|
||||
func NewIPCIDR(s string, adapter string) *IPCIDR {
|
||||
|
@ -7,21 +7,36 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
INFO LogType = iota
|
||||
ERROR LogLevel = iota
|
||||
WARNING
|
||||
ERROR
|
||||
INFO
|
||||
DEBUG
|
||||
)
|
||||
|
||||
type LogType int
|
||||
type LogLevel int
|
||||
|
||||
type Log struct {
|
||||
LogType LogType
|
||||
Payload string
|
||||
LogLevel LogLevel
|
||||
Payload string
|
||||
}
|
||||
|
||||
func (l *Log) Type() string {
|
||||
switch l.LogLevel {
|
||||
case INFO:
|
||||
return "Info"
|
||||
case WARNING:
|
||||
return "Warning"
|
||||
case ERROR:
|
||||
return "Error"
|
||||
case DEBUG:
|
||||
return "Debug"
|
||||
default:
|
||||
return "Unknow"
|
||||
}
|
||||
}
|
||||
|
||||
func print(data Log) {
|
||||
switch data.LogType {
|
||||
switch data.LogLevel {
|
||||
case INFO:
|
||||
log.Infoln(data.Payload)
|
||||
case WARNING:
|
||||
@ -44,9 +59,9 @@ func (t *Tunnel) subscribeLogs() {
|
||||
}
|
||||
}
|
||||
|
||||
func newLog(logType LogType, format string, v ...interface{}) Log {
|
||||
func newLog(logLevel LogLevel, format string, v ...interface{}) Log {
|
||||
return Log{
|
||||
LogType: logType,
|
||||
Payload: fmt.Sprintf(format, v...),
|
||||
LogLevel: logLevel,
|
||||
Payload: fmt.Sprintf(format, v...),
|
||||
}
|
||||
}
|
||||
|
22
tunnel/mode.go
Normal file
22
tunnel/mode.go
Normal file
@ -0,0 +1,22 @@
|
||||
package tunnel
|
||||
|
||||
type Mode int
|
||||
|
||||
const (
|
||||
Global Mode = iota
|
||||
Rule
|
||||
Direct
|
||||
)
|
||||
|
||||
func (m Mode) String() string {
|
||||
switch m {
|
||||
case Global:
|
||||
return "Global"
|
||||
case Rule:
|
||||
return "Rule"
|
||||
case Direct:
|
||||
return "Direct"
|
||||
default:
|
||||
return "Unknow"
|
||||
}
|
||||
}
|
153
tunnel/tunnel.go
153
tunnel/tunnel.go
@ -2,8 +2,10 @@ package tunnel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/adapters"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
@ -25,35 +27,55 @@ type Tunnel struct {
|
||||
observable *observable.Observable
|
||||
logCh chan interface{}
|
||||
configLock *sync.RWMutex
|
||||
traffic *C.Traffic
|
||||
mode Mode
|
||||
selector *adapters.Selector
|
||||
}
|
||||
|
||||
func (t *Tunnel) Add(req C.ServerAdapter) {
|
||||
t.queue.In() <- req
|
||||
}
|
||||
|
||||
func (t *Tunnel) UpdateConfig() (err error) {
|
||||
t.configLock.Lock()
|
||||
defer t.configLock.Unlock()
|
||||
func (t *Tunnel) Traffic() *C.Traffic {
|
||||
return t.traffic
|
||||
}
|
||||
|
||||
func (t *Tunnel) Config() ([]C.Rule, map[string]C.Proxy) {
|
||||
return t.rules, t.proxys
|
||||
}
|
||||
|
||||
func (t *Tunnel) Log() *observable.Observable {
|
||||
return t.observable
|
||||
}
|
||||
|
||||
func (t *Tunnel) SetMode(mode Mode) {
|
||||
t.mode = mode
|
||||
}
|
||||
|
||||
func (t *Tunnel) GetMode() Mode {
|
||||
return t.mode
|
||||
}
|
||||
|
||||
func (t *Tunnel) UpdateConfig() (err error) {
|
||||
cfg, err := C.GetConfig()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// clear proxys and rules
|
||||
t.proxys = make(map[string]C.Proxy)
|
||||
t.rules = []C.Rule{}
|
||||
// empty proxys and rules
|
||||
proxys := make(map[string]C.Proxy)
|
||||
rules := []C.Rule{}
|
||||
|
||||
proxys := cfg.Section("Proxy")
|
||||
rules := cfg.Section("Rule")
|
||||
proxysConfig := cfg.Section("Proxy")
|
||||
rulesConfig := cfg.Section("Rule")
|
||||
groupsConfig := cfg.Section("Proxy Group")
|
||||
|
||||
// parse proxy
|
||||
for _, key := range proxys.Keys() {
|
||||
proxy := strings.Split(key.Value(), ",")
|
||||
for _, key := range proxysConfig.Keys() {
|
||||
proxy := key.Strings(",")
|
||||
if len(proxy) == 0 {
|
||||
continue
|
||||
}
|
||||
proxy = trimArr(proxy)
|
||||
switch proxy[0] {
|
||||
// ss, server, port, cipter, password
|
||||
case "ss":
|
||||
@ -61,16 +83,16 @@ func (t *Tunnel) UpdateConfig() (err error) {
|
||||
continue
|
||||
}
|
||||
ssURL := fmt.Sprintf("ss://%s:%s@%s:%s", proxy[3], proxy[4], proxy[1], proxy[2])
|
||||
t.proxys[key.Name()] = adapters.NewShadowSocks(ssURL)
|
||||
ss, err := adapters.NewShadowSocks(key.Name(), ssURL, t.traffic)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
proxys[key.Name()] = ss
|
||||
}
|
||||
}
|
||||
|
||||
// init proxy
|
||||
t.proxys["DIRECT"] = adapters.NewDirect()
|
||||
t.proxys["REJECT"] = adapters.NewReject()
|
||||
|
||||
// parse rules
|
||||
for _, key := range rules.Keys() {
|
||||
for _, key := range rulesConfig.Keys() {
|
||||
rule := strings.Split(key.Name(), ",")
|
||||
if len(rule) < 3 {
|
||||
continue
|
||||
@ -78,18 +100,87 @@ func (t *Tunnel) UpdateConfig() (err error) {
|
||||
rule = trimArr(rule)
|
||||
switch rule[0] {
|
||||
case "DOMAIN-SUFFIX":
|
||||
t.rules = append(t.rules, R.NewDomainSuffix(rule[1], rule[2]))
|
||||
rules = append(rules, R.NewDomainSuffix(rule[1], rule[2]))
|
||||
case "DOMAIN-KEYWORD":
|
||||
t.rules = append(t.rules, R.NewDomainKeyword(rule[1], rule[2]))
|
||||
rules = append(rules, R.NewDomainKeyword(rule[1], rule[2]))
|
||||
case "GEOIP":
|
||||
t.rules = append(t.rules, R.NewGEOIP(rule[1], rule[2]))
|
||||
rules = append(rules, R.NewGEOIP(rule[1], rule[2]))
|
||||
case "IP-CIDR", "IP-CIDR6":
|
||||
t.rules = append(t.rules, R.NewIPCIDR(rule[1], rule[2]))
|
||||
rules = append(rules, R.NewIPCIDR(rule[1], rule[2]))
|
||||
case "FINAL":
|
||||
t.rules = append(t.rules, R.NewFinal(rule[2]))
|
||||
rules = append(rules, R.NewFinal(rule[2]))
|
||||
}
|
||||
}
|
||||
|
||||
// parse proxy groups
|
||||
for _, key := range groupsConfig.Keys() {
|
||||
rule := strings.Split(key.Value(), ",")
|
||||
rule = trimArr(rule)
|
||||
switch rule[0] {
|
||||
case "url-test":
|
||||
if len(rule) < 4 {
|
||||
return fmt.Errorf("URLTest need more than 4 param")
|
||||
}
|
||||
proxyNames := rule[1 : len(rule)-2]
|
||||
delay, _ := strconv.Atoi(rule[len(rule)-1])
|
||||
url := rule[len(rule)-2]
|
||||
var ps []C.Proxy
|
||||
for _, name := range proxyNames {
|
||||
if p, ok := proxys[name]; ok {
|
||||
ps = append(ps, p)
|
||||
}
|
||||
}
|
||||
|
||||
adapter, err := adapters.NewURLTest(key.Name(), ps, url, time.Duration(delay)*time.Second)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Config error: %s", err.Error())
|
||||
}
|
||||
proxys[key.Name()] = adapter
|
||||
case "select":
|
||||
if len(rule) < 3 {
|
||||
return fmt.Errorf("Selector need more than 3 param")
|
||||
}
|
||||
proxyNames := rule[1:]
|
||||
selectProxy := make(map[string]C.Proxy)
|
||||
for _, name := range proxyNames {
|
||||
proxy, exist := proxys[name]
|
||||
if !exist {
|
||||
return fmt.Errorf("Proxy %s not exist", name)
|
||||
}
|
||||
selectProxy[name] = proxy
|
||||
}
|
||||
selector, err := adapters.NewSelector(key.Name(), selectProxy)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Selector create error: %s", err.Error())
|
||||
}
|
||||
proxys[key.Name()] = selector
|
||||
}
|
||||
}
|
||||
|
||||
// init proxy
|
||||
proxys["DIRECT"] = adapters.NewDirect(t.traffic)
|
||||
proxys["REJECT"] = adapters.NewReject()
|
||||
|
||||
t.configLock.Lock()
|
||||
defer t.configLock.Unlock()
|
||||
|
||||
// stop url-test
|
||||
for _, elm := range t.proxys {
|
||||
urlTest, ok := elm.(*adapters.URLTest)
|
||||
if ok {
|
||||
urlTest.Close()
|
||||
}
|
||||
}
|
||||
|
||||
s, err := adapters.NewSelector("Proxy", proxys)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.proxys = proxys
|
||||
t.rules = rules
|
||||
t.selector = s
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -105,7 +196,17 @@ func (t *Tunnel) process() {
|
||||
func (t *Tunnel) handleConn(localConn C.ServerAdapter) {
|
||||
defer localConn.Close()
|
||||
addr := localConn.Addr()
|
||||
proxy := t.match(addr)
|
||||
|
||||
var proxy C.Proxy
|
||||
switch t.mode {
|
||||
case Direct:
|
||||
proxy = t.proxys["DIRECT"]
|
||||
case Global:
|
||||
proxy = t.selector
|
||||
// Rule
|
||||
default:
|
||||
proxy = t.match(addr)
|
||||
}
|
||||
remoConn, err := proxy.Generator(addr)
|
||||
if err != nil {
|
||||
t.logCh <- newLog(WARNING, "Proxy connect error: %s", err.Error())
|
||||
@ -126,11 +227,11 @@ func (t *Tunnel) match(addr *C.Addr) C.Proxy {
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
t.logCh <- newLog(INFO, "%v match %d using %s", addr.String(), rule.RuleType(), rule.Adapter())
|
||||
t.logCh <- newLog(INFO, "%v match %s using %s", addr.String(), rule.RuleType().String(), rule.Adapter())
|
||||
return a
|
||||
}
|
||||
}
|
||||
t.logCh <- newLog(INFO, "don't find, direct")
|
||||
t.logCh <- newLog(INFO, "%v doesn't match any rule using DIRECT", addr.String())
|
||||
return t.proxys["DIRECT"]
|
||||
}
|
||||
|
||||
@ -142,6 +243,8 @@ func newTunnel() *Tunnel {
|
||||
observable: observable.NewObservable(logCh),
|
||||
logCh: logCh,
|
||||
configLock: &sync.RWMutex{},
|
||||
traffic: C.NewTraffic(time.Second),
|
||||
mode: Rule,
|
||||
}
|
||||
go tunnel.process()
|
||||
go tunnel.subscribeLogs()
|
||||
|
Reference in New Issue
Block a user