Improve: config convergent and add log-level

This commit is contained in:
Dreamacro
2018-07-26 00:04:59 +08:00
parent 7357d2d0c2
commit 8389150318
31 changed files with 757 additions and 484 deletions

67
tunnel/connection.go Normal file
View File

@ -0,0 +1,67 @@
package tunnel
import (
"io"
"net"
"net/http"
"time"
"github.com/Dreamacro/clash/adapters/local"
C "github.com/Dreamacro/clash/constant"
)
func (t *Tunnel) handleHTTP(request *adapters.HttpAdapter, proxy C.ProxyAdapter) {
req := http.Transport{
Dial: func(string, string) (net.Conn, error) {
conn := newTrafficTrack(proxy.Conn(), t.traffic)
return conn, nil
},
// from http.DefaultTransport
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
resp, err := req.RoundTrip(request.R)
if err != nil {
return
}
defer resp.Body.Close()
header := request.W.Header()
for k, vv := range resp.Header {
for _, v := range vv {
header.Add(k, v)
}
}
request.W.WriteHeader(resp.StatusCode)
var writer io.Writer = request.W
if len(resp.TransferEncoding) > 0 && resp.TransferEncoding[0] == "chunked" {
writer = ChunkWriter{Writer: request.W}
}
io.Copy(writer, resp.Body)
}
func (t *Tunnel) handleHTTPS(request *adapters.HttpsAdapter, proxy C.ProxyAdapter) {
conn := newTrafficTrack(proxy.Conn(), t.traffic)
go io.Copy(request.Conn(), conn)
io.Copy(conn, request.Conn())
}
func (t *Tunnel) handleSOCKS(request *adapters.SocksAdapter, proxy C.ProxyAdapter) {
conn := newTrafficTrack(proxy.Conn(), t.traffic)
go io.Copy(request.Conn(), conn)
io.Copy(conn, request.Conn())
}
// ChunkWriter is a writer wrapper and used when TransferEncoding is chunked
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
}

View File

@ -3,47 +3,29 @@ package tunnel
import (
"fmt"
C "github.com/Dreamacro/clash/constant"
log "github.com/sirupsen/logrus"
)
const (
ERROR LogLevel = iota
WARNING
INFO
DEBUG
)
type LogLevel int
type Log struct {
LogLevel LogLevel
LogLevel C.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"
}
return l.LogLevel.String()
}
func print(data Log) {
switch data.LogLevel {
case INFO:
case C.INFO:
log.Infoln(data.Payload)
case WARNING:
case C.WARNING:
log.Warnln(data.Payload)
case ERROR:
case C.ERROR:
log.Errorln(data.Payload)
case DEBUG:
case C.DEBUG:
log.Debugln(data.Payload)
}
}
@ -55,11 +37,13 @@ func (t *Tunnel) subscribeLogs() {
}
for elm := range sub {
data := elm.(Log)
print(data)
if data.LogLevel <= t.logLevel {
print(data)
}
}
}
func newLog(logLevel LogLevel, format string, v ...interface{}) Log {
func newLog(logLevel C.LogLevel, format string, v ...interface{}) Log {
return Log{
LogLevel: logLevel,
Payload: fmt.Sprintf(format, v...),

View File

@ -1,22 +0,0 @@
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"
}
}

View File

@ -1,16 +1,14 @@
package tunnel
import (
"fmt"
"strconv"
"strings"
"sync"
"time"
"github.com/Dreamacro/clash/adapters"
LocalAdapter "github.com/Dreamacro/clash/adapters/local"
RemoteAdapter "github.com/Dreamacro/clash/adapters/remote"
cfg "github.com/Dreamacro/clash/config"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/observable"
R "github.com/Dreamacro/clash/rules"
"gopkg.in/eapache/channels.v1"
)
@ -20,168 +18,61 @@ var (
once sync.Once
)
// Tunnel handle proxy socket and HTTP/SOCKS socket
type Tunnel struct {
queue *channels.InfiniteChannel
rules []C.Rule
proxies map[string]C.Proxy
observable *observable.Observable
logCh chan interface{}
configLock *sync.RWMutex
traffic *C.Traffic
mode Mode
selector *adapters.Selector
// Outbound Rule
mode cfg.Mode
selector *RemoteAdapter.Selector
// Log
logCh chan interface{}
observable *observable.Observable
logLevel C.LogLevel
}
// Add request to queue
func (t *Tunnel) Add(req C.ServerAdapter) {
t.queue.In() <- req
}
// Traffic return traffic of all connections
func (t *Tunnel) Traffic() *C.Traffic {
return t.traffic
}
func (t *Tunnel) Config() ([]C.Rule, map[string]C.Proxy) {
return t.rules, t.proxies
}
// Log return clash log stream
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
}
// empty proxies and rules
proxies := make(map[string]C.Proxy)
rules := []C.Rule{}
proxiesConfig := cfg.Section("Proxy")
rulesConfig := cfg.Section("Rule")
groupsConfig := cfg.Section("Proxy Group")
// parse proxy
for _, key := range proxiesConfig.Keys() {
proxy := key.Strings(",")
if len(proxy) == 0 {
continue
}
switch proxy[0] {
// ss, server, port, cipter, password
case "ss":
if len(proxy) < 5 {
continue
}
ssURL := fmt.Sprintf("ss://%s:%s@%s:%s", proxy[3], proxy[4], proxy[1], proxy[2])
ss, err := adapters.NewShadowSocks(key.Name(), ssURL, t.traffic)
if err != nil {
return err
}
proxies[key.Name()] = ss
func (t *Tunnel) configMonitor(signal chan<- struct{}) {
sub := cfg.Instance().Subscribe()
signal <- struct{}{}
for elm := range sub {
event := elm.(*cfg.Event)
switch event.Type {
case "proxies":
proxies := event.Payload.(map[string]C.Proxy)
t.configLock.Lock()
t.proxies = proxies
t.configLock.Unlock()
case "rules":
rules := event.Payload.([]C.Rule)
t.configLock.Lock()
t.rules = rules
t.configLock.Unlock()
case "mode":
t.mode = event.Payload.(cfg.Mode)
case "log-level":
t.logLevel = event.Payload.(C.LogLevel)
}
}
// parse rules
for _, key := range rulesConfig.Keys() {
rule := strings.Split(key.Name(), ",")
if len(rule) < 3 {
continue
}
rule = trimArr(rule)
switch rule[0] {
case "DOMAIN-SUFFIX":
rules = append(rules, R.NewDomainSuffix(rule[1], rule[2]))
case "DOMAIN-KEYWORD":
rules = append(rules, R.NewDomainKeyword(rule[1], rule[2]))
case "GEOIP":
rules = append(rules, R.NewGEOIP(rule[1], rule[2]))
case "IP-CIDR", "IP-CIDR6":
rules = append(rules, R.NewIPCIDR(rule[1], rule[2]))
case "FINAL":
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 := proxies[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())
}
proxies[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 := proxies[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())
}
proxies[key.Name()] = selector
}
}
// init proxy
proxies["DIRECT"] = adapters.NewDirect(t.traffic)
proxies["REJECT"] = adapters.NewReject()
t.configLock.Lock()
defer t.configLock.Unlock()
// stop url-test
for _, elm := range t.proxies {
urlTest, ok := elm.(*adapters.URLTest)
if ok {
urlTest.Close()
}
}
s, err := adapters.NewSelector("Proxy", proxies)
if err != nil {
return err
}
t.proxies = proxies
t.rules = rules
t.selector = s
return nil
}
func (t *Tunnel) process() {
@ -199,9 +90,9 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) {
var proxy C.Proxy
switch t.mode {
case Direct:
case cfg.Direct:
proxy = t.proxies["DIRECT"]
case Global:
case cfg.Global:
proxy = t.selector
// Rule
default:
@ -209,12 +100,22 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) {
}
remoConn, err := proxy.Generator(addr)
if err != nil {
t.logCh <- newLog(WARNING, "Proxy connect error: %s", err.Error())
t.logCh <- newLog(C.WARNING, "Proxy connect error: %s", err.Error())
return
}
defer remoConn.Close()
localConn.Connect(remoConn)
switch adapter := localConn.(type) {
case *LocalAdapter.HttpAdapter:
t.handleHTTP(adapter, remoConn)
break
case *LocalAdapter.HttpsAdapter:
t.handleHTTPS(adapter, remoConn)
break
case *LocalAdapter.SocksAdapter:
t.handleSOCKS(adapter, remoConn)
break
}
}
func (t *Tunnel) match(addr *C.Addr) C.Proxy {
@ -227,31 +128,39 @@ func (t *Tunnel) match(addr *C.Addr) C.Proxy {
if !ok {
continue
}
t.logCh <- newLog(INFO, "%v match %s using %s", addr.String(), rule.RuleType().String(), rule.Adapter())
t.logCh <- newLog(C.INFO, "%v match %s using %s", addr.String(), rule.RuleType().String(), rule.Adapter())
return a
}
}
t.logCh <- newLog(INFO, "%v doesn't match any rule using DIRECT", addr.String())
t.logCh <- newLog(C.INFO, "%v doesn't match any rule using DIRECT", addr.String())
return t.proxies["DIRECT"]
}
// Run initial task
func (t *Tunnel) Run() {
go t.process()
go t.subscribeLogs()
signal := make(chan struct{})
go t.configMonitor(signal)
<-signal
}
func newTunnel() *Tunnel {
logCh := make(chan interface{})
tunnel := &Tunnel{
return &Tunnel{
queue: channels.NewInfiniteChannel(),
proxies: make(map[string]C.Proxy),
observable: observable.NewObservable(logCh),
logCh: logCh,
configLock: &sync.RWMutex{},
traffic: C.NewTraffic(time.Second),
mode: Rule,
mode: cfg.Rule,
logLevel: C.INFO,
}
go tunnel.process()
go tunnel.subscribeLogs()
return tunnel
}
func GetInstance() *Tunnel {
// Instance return singleton instance of Tunnel
func Instance() *Tunnel {
once.Do(func() {
tunnel = newTunnel()
})

29
tunnel/util.go Normal file
View File

@ -0,0 +1,29 @@
package tunnel
import (
"net"
C "github.com/Dreamacro/clash/constant"
)
// TrafficTrack record traffic of net.Conn
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}
}

View File

@ -1,12 +0,0 @@
package tunnel
import (
"strings"
)
func trimArr(arr []string) (r []string) {
for _, e := range arr {
r = append(r, strings.Trim(e, " "))
}
return
}