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

32
adapters/local/http.go Normal file
View file

@ -0,0 +1,32 @@
package adapters
import (
"net/http"
C "github.com/Dreamacro/clash/constant"
)
type HttpAdapter struct {
addr *C.Addr
R *http.Request
W http.ResponseWriter
done chan struct{}
}
func (h *HttpAdapter) Close() {
h.done <- struct{}{}
}
func (h *HttpAdapter) Addr() *C.Addr {
return h.addr
}
func NewHttp(host string, w http.ResponseWriter, r *http.Request) (*HttpAdapter, chan struct{}) {
done := make(chan struct{})
return &HttpAdapter{
addr: parseHttpAddr(host),
R: r,
W: w,
done: done,
}, done
}

View file

@ -1,8 +1,7 @@
package http package adapters
import ( import (
"bufio" "bufio"
"io"
"net" "net"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
@ -22,9 +21,8 @@ func (h *HttpsAdapter) Addr() *C.Addr {
return h.addr return h.addr
} }
func (h *HttpsAdapter) Connect(proxy C.ProxyAdapter) { func (h *HttpsAdapter) Conn() net.Conn {
go io.Copy(h.conn, proxy.ReadWriter()) return h.conn
io.Copy(proxy.ReadWriter(), h.conn)
} }
func NewHttps(host string, conn net.Conn) *HttpsAdapter { func NewHttps(host string, conn net.Conn) *HttpsAdapter {

32
adapters/local/socks.go Normal file
View file

@ -0,0 +1,32 @@
package adapters
import (
"net"
C "github.com/Dreamacro/clash/constant"
"github.com/riobard/go-shadowsocks2/socks"
)
type SocksAdapter struct {
conn net.Conn
addr *C.Addr
}
func (s *SocksAdapter) Close() {
s.conn.Close()
}
func (s *SocksAdapter) Addr() *C.Addr {
return s.addr
}
func (s *SocksAdapter) Conn() net.Conn {
return s.conn
}
func NewSocks(target socks.Addr, conn net.Conn) *SocksAdapter {
return &SocksAdapter{
conn: conn,
addr: parseSocksAddr(target),
}
}

66
adapters/local/util.go Normal file
View file

@ -0,0 +1,66 @@
package adapters
import (
"net"
"strconv"
C "github.com/Dreamacro/clash/constant"
"github.com/riobard/go-shadowsocks2/socks"
)
func parseSocksAddr(target socks.Addr) *C.Addr {
var host, port string
var ip net.IP
switch target[0] {
case socks.AtypDomainName:
host = string(target[2 : 2+target[1]])
port = strconv.Itoa((int(target[2+target[1]]) << 8) | int(target[2+target[1]+1]))
ipAddr, err := net.ResolveIPAddr("ip", host)
if err == nil {
ip = ipAddr.IP
}
case socks.AtypIPv4:
ip = net.IP(target[1 : 1+net.IPv4len])
port = strconv.Itoa((int(target[1+net.IPv4len]) << 8) | int(target[1+net.IPv4len+1]))
case socks.AtypIPv6:
ip = net.IP(target[1 : 1+net.IPv6len])
port = strconv.Itoa((int(target[1+net.IPv6len]) << 8) | int(target[1+net.IPv6len+1]))
}
return &C.Addr{
NetWork: C.TCP,
AddrType: int(target[0]),
Host: host,
IP: &ip,
Port: port,
}
}
func parseHttpAddr(target string) *C.Addr {
host, port, _ := net.SplitHostPort(target)
ipAddr, err := net.ResolveIPAddr("ip", host)
var resolveIP *net.IP
if err == nil {
resolveIP = &ipAddr.IP
}
var addType int
ip := net.ParseIP(host)
switch {
case ip == nil:
addType = socks.AtypDomainName
case ip.To4() == nil:
addType = socks.AtypIPv6
default:
addType = socks.AtypIPv4
}
return &C.Addr{
NetWork: C.TCP,
AddrType: addType,
Host: host,
IP: resolveIP,
Port: port,
}
}

View file

@ -1,7 +1,6 @@
package adapters package adapters
import ( import (
"io"
"net" "net"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
@ -12,11 +11,6 @@ type DirectAdapter struct {
conn net.Conn conn net.Conn
} }
// ReadWriter is used to handle network traffic
func (d *DirectAdapter) ReadWriter() io.ReadWriter {
return d.conn
}
// Close is used to close connection // Close is used to close connection
func (d *DirectAdapter) Close() { func (d *DirectAdapter) Close() {
d.conn.Close() d.conn.Close()
@ -27,9 +21,7 @@ func (d *DirectAdapter) Conn() net.Conn {
return d.conn return d.conn
} }
type Direct struct { type Direct struct{}
traffic *C.Traffic
}
func (d *Direct) Name() string { func (d *Direct) Name() string {
return "Direct" return "Direct"
@ -45,9 +37,9 @@ func (d *Direct) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err error) {
return return
} }
c.(*net.TCPConn).SetKeepAlive(true) c.(*net.TCPConn).SetKeepAlive(true)
return &DirectAdapter{conn: NewTrafficTrack(c, d.traffic)}, nil return &DirectAdapter{conn: c}, nil
} }
func NewDirect(traffic *C.Traffic) *Direct { func NewDirect() *Direct {
return &Direct{traffic: traffic} return &Direct{}
} }

View file

@ -11,11 +11,6 @@ import (
type RejectAdapter struct { type RejectAdapter struct {
} }
// ReadWriter is used to handle network traffic
func (r *RejectAdapter) ReadWriter() io.ReadWriter {
return &NopRW{}
}
// Close is used to close connection // Close is used to close connection
func (r *RejectAdapter) Close() {} func (r *RejectAdapter) Close() {}

View file

@ -3,7 +3,6 @@ package adapters
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"io"
"net" "net"
"net/url" "net/url"
"strconv" "strconv"
@ -19,11 +18,6 @@ type ShadowsocksAdapter struct {
conn net.Conn conn net.Conn
} }
// ReadWriter is used to handle network traffic
func (ss *ShadowsocksAdapter) ReadWriter() io.ReadWriter {
return ss.conn
}
// Close is used to close connection // Close is used to close connection
func (ss *ShadowsocksAdapter) Close() { func (ss *ShadowsocksAdapter) Close() {
ss.conn.Close() ss.conn.Close()
@ -34,10 +28,9 @@ func (ss *ShadowsocksAdapter) Conn() net.Conn {
} }
type ShadowSocks struct { type ShadowSocks struct {
server string server string
name string name string
cipher core.Cipher cipher core.Cipher
traffic *C.Traffic
} }
func (ss *ShadowSocks) Name() string { func (ss *ShadowSocks) Name() string {
@ -56,10 +49,10 @@ func (ss *ShadowSocks) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err erro
c.(*net.TCPConn).SetKeepAlive(true) c.(*net.TCPConn).SetKeepAlive(true)
c = ss.cipher.StreamConn(c) c = ss.cipher.StreamConn(c)
_, err = c.Write(serializesSocksAddr(addr)) _, err = c.Write(serializesSocksAddr(addr))
return &ShadowsocksAdapter{conn: NewTrafficTrack(c, ss.traffic)}, err return &ShadowsocksAdapter{conn: c}, err
} }
func NewShadowSocks(name string, ssURL string, traffic *C.Traffic) (*ShadowSocks, error) { func NewShadowSocks(name string, ssURL string) (*ShadowSocks, error) {
var key []byte var key []byte
server, cipher, password, _ := parseURL(ssURL) server, cipher, password, _ := parseURL(ssURL)
ciph, err := core.PickCipher(cipher, key, password) ciph, err := core.PickCipher(cipher, key, password)
@ -67,10 +60,9 @@ func NewShadowSocks(name string, ssURL string, traffic *C.Traffic) (*ShadowSocks
return nil, fmt.Errorf("ss %s initialize error: %s", server, err.Error()) return nil, fmt.Errorf("ss %s initialize error: %s", server, err.Error())
} }
return &ShadowSocks{ return &ShadowSocks{
server: server, server: server,
name: name, name: name,
cipher: ciph, cipher: ciph,
traffic: traffic,
}, nil }, nil
} }

296
config/config.go Normal file
View file

@ -0,0 +1,296 @@
package config
import (
"fmt"
"os"
"strconv"
"strings"
"sync"
"time"
"github.com/Dreamacro/clash/adapters/remote"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/observable"
R "github.com/Dreamacro/clash/rules"
"gopkg.in/ini.v1"
)
var (
config *Config
once sync.Once
)
// Config is clash config manager
type Config struct {
general *General
rules []C.Rule
proxies map[string]C.Proxy
lastUpdate time.Time
event chan<- interface{}
errCh chan interface{}
observable *observable.Observable
}
// Event is event of clash config
type Event struct {
Type string
Payload interface{}
}
// Subscribe config stream
func (c *Config) Subscribe() observable.Subscription {
sub, _ := c.observable.Subscribe()
return sub
}
// Report return a channel for collecting error message
func (c *Config) Report() chan<- interface{} {
return c.errCh
}
func (c *Config) readConfig() (*ini.File, error) {
if _, err := os.Stat(C.ConfigPath); os.IsNotExist(err) {
return nil, err
}
return ini.LoadSources(
ini.LoadOptions{AllowBooleanKeys: true},
C.ConfigPath,
)
}
// Parse config
func (c *Config) Parse() error {
cfg, err := c.readConfig()
if err != nil {
return err
}
if err := c.parseGeneral(cfg); err != nil {
return err
}
if err := c.parseProxies(cfg); err != nil {
return err
}
return c.parseRules(cfg)
}
// Proxies return proxies of clash
func (c *Config) Proxies() map[string]C.Proxy {
return c.proxies
}
// Rules return rules of clash
func (c *Config) Rules() []C.Rule {
return c.rules
}
// SetMode change mode of clash
func (c *Config) SetMode(mode Mode) {
c.general.Mode = mode
c.event <- &Event{Type: "mode", Payload: mode}
}
// General return clash general config
func (c *Config) General() General {
return *c.general
}
// UpdateRules is a function for hot reload rules
func (c *Config) UpdateRules() error {
cfg, err := c.readConfig()
if err != nil {
return err
}
return c.parseRules(cfg)
}
func (c *Config) parseGeneral(cfg *ini.File) 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()
logLevelString := general.Key("log-level").MustString(C.INFO.String())
modeString := general.Key("mode").MustString(Rule.String())
mode, exist := ModeMapping[modeString]
if !exist {
return fmt.Errorf("General.mode value invalid")
}
logLevel, exist := C.LogLevelMapping[logLevelString]
if !exist {
return fmt.Errorf("General.log-level value invalid")
}
c.general = &General{
Base: &Base{
Port: &port,
SocketPort: &socksPort,
AllowLan: &allowLan,
},
Mode: mode,
LogLevel: logLevel,
}
if restAddr := general.Key("external-controller").String(); restAddr != "" {
c.event <- &Event{Type: "external-controller", Payload: restAddr}
}
c.UpdateGeneral(*c.general)
return nil
}
// UpdateGeneral dispatch update event
func (c *Config) UpdateGeneral(general General) {
c.event <- &Event{Type: "base", Payload: *general.Base}
c.event <- &Event{Type: "mode", Payload: general.Mode}
c.event <- &Event{Type: "log-level", Payload: general.LogLevel}
}
func (c *Config) parseProxies(cfg *ini.File) error {
proxies := make(map[string]C.Proxy)
proxiesConfig := cfg.Section("Proxy")
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)
if err != nil {
return err
}
proxies[key.Name()] = ss
}
}
// parse proxy group
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()
proxies["REJECT"] = adapters.NewReject()
c.proxies = proxies
c.event <- &Event{Type: "proxies", Payload: proxies}
return nil
}
func (c *Config) parseRules(cfg *ini.File) error {
rules := []C.Rule{}
rulesConfig := cfg.Section("Rule")
// 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]))
}
}
c.rules = rules
c.event <- &Event{Type: "rules", Payload: rules}
return nil
}
func (c *Config) handleErrorMessage() {
for elm := range c.errCh {
event := elm.(Event)
switch event.Type {
case "base":
c.general.Base = event.Payload.(*Base)
}
}
}
func newConfig() *Config {
event := make(chan interface{})
config := &Config{
general: &General{},
proxies: make(map[string]C.Proxy),
rules: []C.Rule{},
lastUpdate: time.Now(),
event: event,
observable: observable.NewObservable(event),
}
go config.handleErrorMessage()
return config
}
func Instance() *Config {
once.Do(func() {
config = newConfig()
})
return config
}

17
config/general.go Normal file
View file

@ -0,0 +1,17 @@
package config
import (
C "github.com/Dreamacro/clash/constant"
)
type General struct {
*Base
Mode Mode
LogLevel C.LogLevel
}
type Base struct {
Port *int
SocketPort *int
AllowLan *bool
}

View file

@ -1,7 +1,16 @@
package tunnel package config
type Mode int type Mode int
var (
// ModeMapping is a mapping for Mode enum
ModeMapping = map[string]Mode{
"Global": Global,
"Rule": Rule,
"Direct": Direct,
}
)
const ( const (
Global Mode = iota Global Mode = iota
Rule Rule

View file

@ -1,4 +1,4 @@
package tunnel package config
import ( import (
"strings" "strings"

View file

@ -1,7 +1,6 @@
package constant package constant
import ( import (
"io"
"net" "net"
) )
@ -15,14 +14,12 @@ const (
) )
type ProxyAdapter interface { type ProxyAdapter interface {
ReadWriter() io.ReadWriter
Conn() net.Conn Conn() net.Conn
Close() Close()
} }
type ServerAdapter interface { type ServerAdapter interface {
Addr() *Addr Addr() *Addr
Connect(ProxyAdapter)
Close() Close()
} }

View file

@ -10,8 +10,11 @@ const (
AtypDomainName = 3 AtypDomainName = 3
AtypIPv6 = 4 AtypIPv6 = 4
TCP = iota TCP NetWork = iota
UDP UDP
HTTP SourceType = iota
SOCKS
) )
type NetWork int type NetWork int
@ -23,9 +26,12 @@ func (n *NetWork) String() string {
return "udp" return "udp"
} }
type SourceType int
// Addr is used to store connection address // Addr is used to store connection address
type Addr struct { type Addr struct {
NetWork NetWork NetWork NetWork
Source SourceType
AddrType int AddrType int
Host string Host string
IP *net.IP IP *net.IP

View file

@ -11,7 +11,6 @@ import (
"strings" "strings"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"gopkg.in/ini.v1"
) )
const ( const (
@ -107,13 +106,3 @@ func downloadMMDB(path string) (err error) {
return nil return nil
} }
func GetConfig() (*ini.File, error) {
if _, err := os.Stat(ConfigPath); os.IsNotExist(err) {
return nil, err
}
return ini.LoadSources(
ini.LoadOptions{AllowBooleanKeys: true},
ConfigPath,
)
}

35
constant/log.go Normal file
View file

@ -0,0 +1,35 @@
package constant
var (
// LogLevelMapping is a mapping for LogLevel enum
LogLevelMapping = map[string]LogLevel{
"error": ERROR,
"warning": WARNING,
"info": INFO,
"debug": DEBUG,
}
)
const (
ERROR LogLevel = iota
WARNING
INFO
DEBUG
)
type LogLevel int
func (l LogLevel) String() string {
switch l {
case INFO:
return "info"
case WARNING:
return "warning"
case ERROR:
return "error"
case DEBUG:
return "debug"
default:
return "unknow"
}
}

View file

@ -1,12 +1,14 @@
package hub package hub
import ( import (
"github.com/Dreamacro/clash/config"
"github.com/Dreamacro/clash/proxy" "github.com/Dreamacro/clash/proxy"
T "github.com/Dreamacro/clash/tunnel" T "github.com/Dreamacro/clash/tunnel"
) )
var ( var (
tunnel = T.GetInstance() tunnel = T.Instance()
cfg = config.Instance()
listener = proxy.Instance() listener = proxy.Instance()
) )

View file

@ -4,9 +4,9 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"github.com/Dreamacro/clash/config"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/proxy" "github.com/Dreamacro/clash/proxy"
T "github.com/Dreamacro/clash/tunnel"
"github.com/go-chi/chi" "github.com/go-chi/chi"
"github.com/go-chi/render" "github.com/go-chi/render"
@ -19,17 +19,23 @@ func configRouter() http.Handler {
return r return r
} }
var modeMapping = map[string]T.Mode{ type configSchema struct {
"Global": T.Global, Port int `json:"port"`
"Rule": T.Rule, SocketPort int `json:"socket-port"`
"Direct": T.Direct, AllowLan bool `json:"allow-lan"`
Mode string `json:"mode"`
LogLevel string `json:"log-level"`
} }
func getConfigs(w http.ResponseWriter, r *http.Request) { func getConfigs(w http.ResponseWriter, r *http.Request) {
info := listener.Info() general := cfg.General()
mode := tunnel.GetMode().String() render.JSON(w, r, configSchema{
info.Mode = &mode Port: *general.Port,
render.JSON(w, r, info) SocketPort: *general.SocketPort,
AllowLan: *general.AllowLan,
Mode: general.Mode.String(),
LogLevel: general.LogLevel.String(),
})
} }
func updateConfigs(w http.ResponseWriter, r *http.Request) { func updateConfigs(w http.ResponseWriter, r *http.Request) {
@ -48,15 +54,19 @@ func updateConfigs(w http.ResponseWriter, r *http.Request) {
// update proxy // update proxy
listener := proxy.Instance() listener := proxy.Instance()
proxyErr = listener.Update(general.AllowLan, general.Port, general.SocksPort) proxyErr = listener.Update(&config.Base{
AllowLan: general.AllowLan,
Port: general.Port,
SocketPort: general.SocksPort,
})
// update mode // update mode
if general.Mode != nil { if general.Mode != nil {
mode, ok := modeMapping[*general.Mode] mode, ok := config.ModeMapping[*general.Mode]
if !ok { if !ok {
modeErr = fmt.Errorf("Mode error") modeErr = fmt.Errorf("Mode error")
} else { } else {
tunnel.SetMode(mode) cfg.SetMode(mode)
} }
} }

View file

@ -4,7 +4,7 @@ import (
"fmt" "fmt"
"net/http" "net/http"
A "github.com/Dreamacro/clash/adapters" A "github.com/Dreamacro/clash/adapters/remote"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/go-chi/chi" "github.com/go-chi/chi"
@ -61,7 +61,7 @@ type GetProxiesResponse struct {
} }
func getProxies(w http.ResponseWriter, r *http.Request) { func getProxies(w http.ResponseWriter, r *http.Request) {
_, rawProxies := tunnel.Config() rawProxies := cfg.Proxies()
proxies := make(map[string]interface{}) proxies := make(map[string]interface{})
for name, proxy := range rawProxies { for name, proxy := range rawProxies {
proxies[name] = transformProxy(proxy) proxies[name] = transformProxy(proxy)
@ -71,7 +71,7 @@ func getProxies(w http.ResponseWriter, r *http.Request) {
func getProxy(w http.ResponseWriter, r *http.Request) { func getProxy(w http.ResponseWriter, r *http.Request) {
name := chi.URLParam(r, "name") name := chi.URLParam(r, "name")
_, proxies := tunnel.Config() proxies := cfg.Proxies()
proxy, exist := proxies[name] proxy, exist := proxies[name]
if !exist { if !exist {
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
@ -98,7 +98,7 @@ func updateProxy(w http.ResponseWriter, r *http.Request) {
} }
name := chi.URLParam(r, "name") name := chi.URLParam(r, "name")
_, proxies := tunnel.Config() proxies := cfg.Proxies()
proxy, exist := proxies[name] proxy, exist := proxies[name]
if !exist { if !exist {
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)

View file

@ -24,10 +24,10 @@ type GetRulesResponse struct {
} }
func getRules(w http.ResponseWriter, r *http.Request) { func getRules(w http.ResponseWriter, r *http.Request) {
rulesCfg, _ := tunnel.Config() rawRules := cfg.Rules()
var rules []Rule var rules []Rule
for _, rule := range rulesCfg { for _, rule := range rawRules {
rules = append(rules, Rule{ rules = append(rules, Rule{
Name: rule.RuleType().String(), Name: rule.RuleType().String(),
Payload: rule.Payload(), Payload: rule.Payload(),
@ -41,7 +41,7 @@ func getRules(w http.ResponseWriter, r *http.Request) {
} }
func updateRules(w http.ResponseWriter, r *http.Request) { func updateRules(w http.ResponseWriter, r *http.Request) {
err := tunnel.UpdateConfig() err := cfg.UpdateRules()
if err != nil { if err != nil {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
render.JSON(w, r, Error{ render.JSON(w, r, Error{

View file

@ -5,6 +5,8 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/Dreamacro/clash/config"
C "github.com/Dreamacro/clash/constant"
T "github.com/Dreamacro/clash/tunnel" T "github.com/Dreamacro/clash/tunnel"
"github.com/go-chi/chi" "github.com/go-chi/chi"
@ -18,7 +20,19 @@ type Traffic struct {
Down int64 `json:"down"` Down int64 `json:"down"`
} }
func NewHub(addr string) { func newHub(signal chan struct{}) {
var addr string
ch := config.Instance().Subscribe()
signal <- struct{}{}
for {
elm := <-ch
event := elm.(*config.Event)
if event.Type == "external-controller" {
addr = event.Payload.(string)
break
}
}
r := chi.NewRouter() r := chi.NewRouter()
cors := cors.New(cors.Options{ cors := cors.New(cors.Options{
@ -38,7 +52,7 @@ func NewHub(addr string) {
err := http.ListenAndServe(addr, r) err := http.ListenAndServe(addr, r)
if err != nil { if err != nil {
log.Fatalf("External controller error: %s", err.Error()) log.Errorf("External controller error: %s", err.Error())
} }
} }
@ -75,14 +89,7 @@ func getLogs(w http.ResponseWriter, r *http.Request) {
req.Level = "info" req.Level = "info"
} }
mapping := map[string]T.LogLevel{ level, ok := C.LogLevelMapping[req.Level]
"info": T.INFO,
"debug": T.DEBUG,
"error": T.ERROR,
"warning": T.WARNING,
}
level, ok := mapping[req.Level]
if !ok { if !ok {
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
render.JSON(w, r, Error{ render.JSON(w, r, Error{
@ -117,3 +124,10 @@ func getLogs(w http.ResponseWriter, r *http.Request) {
w.(http.Flusher).Flush() w.(http.Flusher).Flush()
} }
} }
// Run initial hub
func Run() {
signal := make(chan struct{})
go newHub(signal)
<-signal
}

22
main.go
View file

@ -5,7 +5,7 @@ import (
"os/signal" "os/signal"
"syscall" "syscall"
C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/config"
"github.com/Dreamacro/clash/hub" "github.com/Dreamacro/clash/hub"
"github.com/Dreamacro/clash/proxy" "github.com/Dreamacro/clash/proxy"
"github.com/Dreamacro/clash/tunnel" "github.com/Dreamacro/clash/tunnel"
@ -14,23 +14,13 @@ import (
) )
func main() { func main() {
if err := tunnel.GetInstance().UpdateConfig(); err != nil { tunnel.Instance().Run()
log.Fatalf("Parse config error: %s", err.Error()) proxy.Instance().Run()
} hub.Run()
if err := proxy.Instance().Run(); err != nil { err := config.Instance().Parse()
log.Fatalf("Proxy listen error: %s", err.Error())
}
// Hub
cfg, err := C.GetConfig()
if err != nil { if err != nil {
log.Fatalf("Read config error: %s", err.Error()) log.Fatalf("Parse config error: %s", err.Error())
}
section := cfg.Section("General")
if key, err := section.GetKey("external-controller"); err == nil {
go hub.NewHub(key.Value())
} }
sigCh := make(chan os.Signal, 1) sigCh := make(chan os.Signal, 1)

View file

@ -1,77 +0,0 @@
package http
import (
"io"
"net"
"net/http"
"time"
C "github.com/Dreamacro/clash/constant"
)
type HttpAdapter struct {
addr *C.Addr
r *http.Request
w http.ResponseWriter
done chan struct{}
}
func (h *HttpAdapter) Close() {
h.done <- struct{}{}
}
func (h *HttpAdapter) Addr() *C.Addr {
return h.addr
}
func (h *HttpAdapter) Connect(proxy C.ProxyAdapter) {
req := http.Transport{
Dial: func(string, string) (net.Conn, error) {
return proxy.Conn(), nil
},
// from http.DefaultTransport
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
resp, err := req.RoundTrip(h.r)
if err != nil {
return
}
defer resp.Body.Close()
header := h.w.Header()
for k, vv := range resp.Header {
for _, v := range vv {
header.Add(k, v)
}
}
h.w.WriteHeader(resp.StatusCode)
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{}) {
done := make(chan struct{})
return &HttpAdapter{
addr: parseHttpAddr(host),
r: r,
w: w,
done: done,
}, done
}

View file

@ -6,15 +6,15 @@ import (
"net/http" "net/http"
"strings" "strings"
"github.com/Dreamacro/clash/adapters/local"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/tunnel" "github.com/Dreamacro/clash/tunnel"
"github.com/riobard/go-shadowsocks2/socks"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
var ( var (
tun = tunnel.GetInstance() tun = tunnel.Instance()
) )
func NewHttpProxy(addr string) (*C.ProxySignal, error) { func NewHttpProxy(addr string) (*C.ProxySignal, error) {
@ -61,7 +61,7 @@ func handleHTTP(w http.ResponseWriter, r *http.Request) {
if !strings.Contains(addr, ":") { if !strings.Contains(addr, ":") {
addr += ":80" addr += ":80"
} }
req, done := NewHttp(addr, w, r) req, done := adapters.NewHttp(addr, w, r)
tun.Add(req) tun.Add(req)
<-done <-done
} }
@ -77,33 +77,5 @@ func handleTunneling(w http.ResponseWriter, r *http.Request) {
} }
// w.WriteHeader(http.StatusOK) doesn't works in Safari // w.WriteHeader(http.StatusOK) doesn't works in Safari
conn.Write([]byte("HTTP/1.1 200 OK\r\n\r\n")) conn.Write([]byte("HTTP/1.1 200 OK\r\n\r\n"))
tun.Add(NewHttps(r.Host, conn)) tun.Add(adapters.NewHttps(r.Host, conn))
}
func parseHttpAddr(target string) *C.Addr {
host, port, _ := net.SplitHostPort(target)
ipAddr, err := net.ResolveIPAddr("ip", host)
var resolveIP *net.IP
if err == nil {
resolveIP = &ipAddr.IP
}
var addType int
ip := net.ParseIP(host)
switch {
case ip == nil:
addType = socks.AtypDomainName
case ip.To4() == nil:
addType = socks.AtypIPv6
default:
addType = socks.AtypIPv4
}
return &C.Addr{
NetWork: C.TCP,
AddrType: addType,
Host: host,
IP: resolveIP,
Port: port,
}
} }

View file

@ -4,11 +4,10 @@ import (
"fmt" "fmt"
"sync" "sync"
"github.com/Dreamacro/clash/config"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/proxy/http" "github.com/Dreamacro/clash/proxy/http"
"github.com/Dreamacro/clash/proxy/socks" "github.com/Dreamacro/clash/proxy/socks"
log "github.com/sirupsen/logrus"
) )
var ( var (
@ -35,24 +34,24 @@ func (l *Listener) Info() (info C.General) {
} }
} }
func (l *Listener) Update(allowLan *bool, httpPort *int, socksPort *int) error { func (l *Listener) Update(base *config.Base) error {
if allowLan != nil { if base.AllowLan != nil {
l.allowLan = *allowLan l.allowLan = *base.AllowLan
} }
var socksErr, httpErr error var socksErr, httpErr error
if allowLan != nil || httpPort != nil { if base.AllowLan != nil || base.Port != nil {
newHTTPPort := l.httpPort newHTTPPort := l.httpPort
if httpPort != nil { if base.Port != nil {
newHTTPPort = *httpPort newHTTPPort = *base.Port
} }
httpErr = l.updateHTTP(newHTTPPort) httpErr = l.updateHTTP(newHTTPPort)
} }
if allowLan != nil || socksPort != nil { if base.AllowLan != nil || base.SocketPort != nil {
newSocksPort := l.socksPort newSocksPort := l.socksPort
if socksPort != nil { if base.SocketPort != nil {
newSocksPort = *socksPort newSocksPort = *base.SocketPort
} }
socksErr = l.updateSocks(newSocksPort) socksErr = l.updateSocks(newSocksPort)
} }
@ -112,29 +111,30 @@ func (l *Listener) genAddr(port int) string {
return fmt.Sprintf("%s:%d", host, port) return fmt.Sprintf("%s:%d", host, port)
} }
func (l *Listener) Run() error { func (l *Listener) process(signal chan<- struct{}) {
return l.Update(&l.allowLan, &l.httpPort, &l.socksPort) sub := config.Instance().Subscribe()
signal <- struct{}{}
for elm := range sub {
event := elm.(*config.Event)
if event.Type == "base" {
base := event.Payload.(config.Base)
l.Update(&base)
}
}
}
// Run ensure config monitoring
func (l *Listener) Run() {
signal := make(chan struct{})
go l.process(signal)
<-signal
} }
func newListener() *Listener { func newListener() *Listener {
cfg, err := C.GetConfig() return &Listener{}
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,
}
} }
// Instance return singleton instance of Listener
func Instance() *Listener { func Instance() *Listener {
once.Do(func() { once.Do(func() {
listener = newListener() listener = newListener()

View file

@ -1,10 +1,9 @@
package socks package socks
import ( import (
"io"
"net" "net"
"strconv"
"github.com/Dreamacro/clash/adapters/local"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/tunnel" "github.com/Dreamacro/clash/tunnel"
@ -13,7 +12,7 @@ import (
) )
var ( var (
tun = tunnel.GetInstance() tun = tunnel.Instance()
) )
func NewSocksProxy(addr string) (*C.ProxySignal, error) { func NewSocksProxy(addr string) (*C.ProxySignal, error) {
@ -60,59 +59,5 @@ func handleSocks(conn net.Conn) {
return return
} }
conn.(*net.TCPConn).SetKeepAlive(true) conn.(*net.TCPConn).SetKeepAlive(true)
tun.Add(NewSocks(target, conn)) tun.Add(adapters.NewSocks(target, conn))
}
type SocksAdapter struct {
conn net.Conn
addr *C.Addr
}
func (s *SocksAdapter) Close() {
s.conn.Close()
}
func (s *SocksAdapter) Addr() *C.Addr {
return s.addr
}
func (s *SocksAdapter) Connect(proxy C.ProxyAdapter) {
go io.Copy(s.conn, proxy.ReadWriter())
io.Copy(proxy.ReadWriter(), s.conn)
}
func parseSocksAddr(target socks.Addr) *C.Addr {
var host, port string
var ip net.IP
switch target[0] {
case socks.AtypDomainName:
host = string(target[2 : 2+target[1]])
port = strconv.Itoa((int(target[2+target[1]]) << 8) | int(target[2+target[1]+1]))
ipAddr, err := net.ResolveIPAddr("ip", host)
if err == nil {
ip = ipAddr.IP
}
case socks.AtypIPv4:
ip = net.IP(target[1 : 1+net.IPv4len])
port = strconv.Itoa((int(target[1+net.IPv4len]) << 8) | int(target[1+net.IPv4len+1]))
case socks.AtypIPv6:
ip = net.IP(target[1 : 1+net.IPv6len])
port = strconv.Itoa((int(target[1+net.IPv6len]) << 8) | int(target[1+net.IPv6len+1]))
}
return &C.Addr{
NetWork: C.TCP,
AddrType: int(target[0]),
Host: host,
IP: &ip,
Port: port,
}
}
func NewSocks(target socks.Addr, conn net.Conn) *SocksAdapter {
return &SocksAdapter{
conn: conn,
addr: parseSocksAddr(target),
}
} }

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

View file

@ -1,16 +1,14 @@
package tunnel package tunnel
import ( import (
"fmt"
"strconv"
"strings"
"sync" "sync"
"time" "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" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/observable" "github.com/Dreamacro/clash/observable"
R "github.com/Dreamacro/clash/rules"
"gopkg.in/eapache/channels.v1" "gopkg.in/eapache/channels.v1"
) )
@ -20,168 +18,61 @@ var (
once sync.Once once sync.Once
) )
// Tunnel handle proxy socket and HTTP/SOCKS socket
type Tunnel struct { type Tunnel struct {
queue *channels.InfiniteChannel queue *channels.InfiniteChannel
rules []C.Rule rules []C.Rule
proxies map[string]C.Proxy proxies map[string]C.Proxy
observable *observable.Observable
logCh chan interface{}
configLock *sync.RWMutex configLock *sync.RWMutex
traffic *C.Traffic 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) { func (t *Tunnel) Add(req C.ServerAdapter) {
t.queue.In() <- req t.queue.In() <- req
} }
// Traffic return traffic of all connections
func (t *Tunnel) Traffic() *C.Traffic { func (t *Tunnel) Traffic() *C.Traffic {
return t.traffic return t.traffic
} }
func (t *Tunnel) Config() ([]C.Rule, map[string]C.Proxy) { // Log return clash log stream
return t.rules, t.proxies
}
func (t *Tunnel) Log() *observable.Observable { func (t *Tunnel) Log() *observable.Observable {
return t.observable return t.observable
} }
func (t *Tunnel) SetMode(mode Mode) { func (t *Tunnel) configMonitor(signal chan<- struct{}) {
t.mode = mode sub := cfg.Instance().Subscribe()
} signal <- struct{}{}
for elm := range sub {
func (t *Tunnel) GetMode() Mode { event := elm.(*cfg.Event)
return t.mode switch event.Type {
} case "proxies":
proxies := event.Payload.(map[string]C.Proxy)
func (t *Tunnel) UpdateConfig() (err error) { t.configLock.Lock()
cfg, err := C.GetConfig() t.proxies = proxies
if err != nil { t.configLock.Unlock()
return case "rules":
} rules := event.Payload.([]C.Rule)
t.configLock.Lock()
// empty proxies and rules t.rules = rules
proxies := make(map[string]C.Proxy) t.configLock.Unlock()
rules := []C.Rule{} case "mode":
t.mode = event.Payload.(cfg.Mode)
proxiesConfig := cfg.Section("Proxy") case "log-level":
rulesConfig := cfg.Section("Rule") t.logLevel = event.Payload.(C.LogLevel)
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
} }
} }
// 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() { func (t *Tunnel) process() {
@ -199,9 +90,9 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) {
var proxy C.Proxy var proxy C.Proxy
switch t.mode { switch t.mode {
case Direct: case cfg.Direct:
proxy = t.proxies["DIRECT"] proxy = t.proxies["DIRECT"]
case Global: case cfg.Global:
proxy = t.selector proxy = t.selector
// Rule // Rule
default: default:
@ -209,12 +100,22 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) {
} }
remoConn, err := proxy.Generator(addr) remoConn, err := proxy.Generator(addr)
if err != nil { 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 return
} }
defer remoConn.Close() 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 { func (t *Tunnel) match(addr *C.Addr) C.Proxy {
@ -227,31 +128,39 @@ func (t *Tunnel) match(addr *C.Addr) C.Proxy {
if !ok { if !ok {
continue 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 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"] 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 { func newTunnel() *Tunnel {
logCh := make(chan interface{}) logCh := make(chan interface{})
tunnel := &Tunnel{ return &Tunnel{
queue: channels.NewInfiniteChannel(), queue: channels.NewInfiniteChannel(),
proxies: make(map[string]C.Proxy), proxies: make(map[string]C.Proxy),
observable: observable.NewObservable(logCh), observable: observable.NewObservable(logCh),
logCh: logCh, logCh: logCh,
configLock: &sync.RWMutex{}, configLock: &sync.RWMutex{},
traffic: C.NewTraffic(time.Second), 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() { once.Do(func() {
tunnel = newTunnel() tunnel = newTunnel()
}) })

View file

@ -1,4 +1,4 @@
package adapters package tunnel
import ( import (
"net" "net"
@ -6,6 +6,7 @@ import (
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
) )
// TrafficTrack record traffic of net.Conn
type TrafficTrack struct { type TrafficTrack struct {
net.Conn net.Conn
traffic *C.Traffic traffic *C.Traffic
@ -23,6 +24,6 @@ func (tt *TrafficTrack) Write(b []byte) (int, error) {
return n, err return n, err
} }
func NewTrafficTrack(conn net.Conn, traffic *C.Traffic) *TrafficTrack { func newTrafficTrack(conn net.Conn, traffic *C.Traffic) *TrafficTrack {
return &TrafficTrack{traffic: traffic, Conn: conn} return &TrafficTrack{traffic: traffic, Conn: conn}
} }