Chore: improve code architecture

This commit is contained in:
Dreamacro 2018-11-21 13:47:46 +08:00
parent 91e35f2f6a
commit 01a477bd3d
37 changed files with 926 additions and 990 deletions

View file

@ -1,6 +1,7 @@
package adapters package adapters
import ( import (
"encoding/json"
"net" "net"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
@ -40,6 +41,12 @@ func (d *Direct) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err er
return &DirectAdapter{conn: c}, nil return &DirectAdapter{conn: c}, nil
} }
func (d *Direct) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{
"type": d.Type().String(),
})
}
func NewDirect() *Direct { func NewDirect() *Direct {
return &Direct{} return &Direct{}
} }

View file

@ -1,6 +1,7 @@
package adapters package adapters
import ( import (
"encoding/json"
"errors" "errors"
"sync" "sync"
"time" "time"
@ -63,6 +64,18 @@ func (f *Fallback) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err
return f.proxies[0].RawProxy.Generator(metadata) return f.proxies[0].RawProxy.Generator(metadata)
} }
func (f *Fallback) MarshalJSON() ([]byte, error) {
var all []string
for _, proxy := range f.proxies {
all = append(all, proxy.RawProxy.Name())
}
return json.Marshal(map[string]interface{}{
"type": f.Type().String(),
"now": f.Now(),
"all": all,
})
}
func (f *Fallback) Close() { func (f *Fallback) Close() {
f.done <- struct{}{} f.done <- struct{}{}
} }

View file

@ -1,6 +1,7 @@
package adapters package adapters
import ( import (
"encoding/json"
"io" "io"
"net" "net"
"time" "time"
@ -36,6 +37,12 @@ func (r *Reject) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err er
return &RejectAdapter{conn: &NopConn{}}, nil return &RejectAdapter{conn: &NopConn{}}, nil
} }
func (r *Reject) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{
"type": r.Type().String(),
})
}
func NewReject() *Reject { func NewReject() *Reject {
return &Reject{} return &Reject{}
} }

View file

@ -1,6 +1,7 @@
package adapters package adapters
import ( import (
"encoding/json"
"errors" "errors"
"sort" "sort"
@ -30,17 +31,21 @@ func (s *Selector) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err
return s.selected.Generator(metadata) return s.selected.Generator(metadata)
} }
func (s *Selector) Now() string { func (s *Selector) MarshalJSON() ([]byte, error) {
return s.selected.Name()
}
func (s *Selector) All() []string {
var all []string var all []string
for k := range s.proxies { for k := range s.proxies {
all = append(all, k) all = append(all, k)
} }
sort.Strings(all) sort.Strings(all)
return all return json.Marshal(map[string]interface{}{
"type": s.Type().String(),
"now": s.Now(),
"all": all,
})
}
func (s *Selector) Now() string {
return s.selected.Name()
} }
func (s *Selector) Set(name string) error { func (s *Selector) Set(name string) error {

View file

@ -2,6 +2,7 @@ package adapters
import ( import (
"bytes" "bytes"
"encoding/json"
"fmt" "fmt"
"net" "net"
"strconv" "strconv"
@ -71,6 +72,12 @@ func (ss *ShadowSocks) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter,
return &ShadowsocksAdapter{conn: c}, err return &ShadowsocksAdapter{conn: c}, err
} }
func (ss *ShadowSocks) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{
"type": ss.Type().String(),
})
}
func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
server := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) server := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
cipher := option.Cipher cipher := option.Cipher

View file

@ -3,6 +3,7 @@ package adapters
import ( import (
"bytes" "bytes"
"crypto/tls" "crypto/tls"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@ -75,6 +76,12 @@ func (ss *Socks5) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err e
return &Socks5Adapter{conn: c}, nil return &Socks5Adapter{conn: c}, nil
} }
func (ss *Socks5) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{
"type": ss.Type().String(),
})
}
func (ss *Socks5) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error { func (ss *Socks5) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
buf := make([]byte, socks.MaxAddrLen) buf := make([]byte, socks.MaxAddrLen)
var err error var err error

View file

@ -1,7 +1,9 @@
package adapters package adapters
import ( import (
"encoding/json"
"errors" "errors"
"sort"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
@ -46,6 +48,19 @@ func (u *URLTest) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err e
return a, err return a, err
} }
func (u *URLTest) MarshalJSON() ([]byte, error) {
var all []string
for _, proxy := range u.proxies {
all = append(all, proxy.Name())
}
sort.Strings(all)
return json.Marshal(map[string]interface{}{
"type": u.Type().String(),
"now": u.Now(),
"all": all,
})
}
func (u *URLTest) Close() { func (u *URLTest) Close() {
u.done <- struct{}{} u.done <- struct{}{}
} }

View file

@ -1,6 +1,7 @@
package adapters package adapters
import ( import (
"encoding/json"
"fmt" "fmt"
"net" "net"
"strconv" "strconv"
@ -43,24 +44,30 @@ type VmessOption struct {
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
} }
func (ss *Vmess) Name() string { func (v *Vmess) Name() string {
return ss.name return v.name
} }
func (ss *Vmess) Type() C.AdapterType { func (v *Vmess) Type() C.AdapterType {
return C.Vmess return C.Vmess
} }
func (ss *Vmess) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) { func (v *Vmess) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) {
c, err := net.DialTimeout("tcp", ss.server, tcpTimeout) c, err := net.DialTimeout("tcp", v.server, tcpTimeout)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error", ss.server) return nil, fmt.Errorf("%s connect error", v.server)
} }
tcpKeepAlive(c) tcpKeepAlive(c)
c, err = ss.client.New(c, parseVmessAddr(metadata)) c, err = v.client.New(c, parseVmessAddr(metadata))
return &VmessAdapter{conn: c}, err return &VmessAdapter{conn: c}, err
} }
func (v *Vmess) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]interface{}{
"type": v.Type().String(),
})
}
func NewVmess(option VmessOption) (*Vmess, error) { func NewVmess(option VmessOption) (*Vmess, error) {
security := strings.ToLower(option.Cipher) security := strings.ToLower(option.Cipher)
client, err := vmess.NewClient(vmess.Config{ client, err := vmess.NewClient(vmess.Config{

View file

@ -5,44 +5,30 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"strings" "strings"
"sync"
"time"
adapters "github.com/Dreamacro/clash/adapters/outbound" adapters "github.com/Dreamacro/clash/adapters/outbound"
"github.com/Dreamacro/clash/common/observable"
"github.com/Dreamacro/clash/common/structure" "github.com/Dreamacro/clash/common/structure"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
R "github.com/Dreamacro/clash/rules" R "github.com/Dreamacro/clash/rules"
T "github.com/Dreamacro/clash/tunnel"
log "github.com/sirupsen/logrus"
yaml "gopkg.in/yaml.v2" yaml "gopkg.in/yaml.v2"
) )
var (
config *Config
once sync.Once
)
// General config // General config
type General struct { type General struct {
Port int Port int `json:"port"`
SocksPort int SocksPort int `json:"socks-port"`
RedirPort int RedirPort int `json:"redir-port"`
AllowLan bool AllowLan bool `json:"allow-lan"`
Mode Mode Mode T.Mode `json:"mode"`
LogLevel C.LogLevel LogLevel log.LogLevel `json:"log-level"`
ExternalController string `json:"external-controller,omitempty"`
Secret string `json:"secret,omitempty"`
} }
// ProxyConfig is update proxy schema type rawConfig struct {
type ProxyConfig struct {
Port *int
SocksPort *int
RedirPort *int
AllowLan *bool
}
// RawConfig is raw config struct
type RawConfig struct {
Port int `yaml:"port"` Port int `yaml:"port"`
SocksPort int `yaml:"socks-port"` SocksPort int `yaml:"socks-port"`
RedirPort int `yaml:"redir-port"` RedirPort int `yaml:"redir-port"`
@ -59,38 +45,16 @@ type RawConfig struct {
// Config is clash config manager // Config is clash config manager
type Config struct { type Config struct {
general *General General *General
rules []C.Rule Rules []C.Rule
proxies map[string]C.Proxy Proxies map[string]C.Proxy
lastUpdate time.Time
event chan<- interface{}
reportCh chan interface{}
observable *observable.Observable
} }
// Event is event of clash config func readConfig(path string) (*rawConfig, error) {
type Event struct { if _, err := os.Stat(path); os.IsNotExist(err) {
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 report message
func (c *Config) Report() chan<- interface{} {
return c.reportCh
}
func (c *Config) readConfig() (*RawConfig, error) {
if _, err := os.Stat(C.Path.Config()); os.IsNotExist(err) {
return nil, err return nil, err
} }
data, err := ioutil.ReadFile(C.Path.Config()) data, err := ioutil.ReadFile(path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -100,10 +64,10 @@ func (c *Config) readConfig() (*RawConfig, error) {
} }
// config with some default value // config with some default value
rawConfig := &RawConfig{ rawConfig := &rawConfig{
AllowLan: false, AllowLan: false,
Mode: Rule.String(), Mode: T.Rule.String(),
LogLevel: C.INFO.String(), LogLevel: log.INFO.String(),
Rule: []string{}, Rule: []string{},
Proxy: []map[string]interface{}{}, Proxy: []map[string]interface{}{},
ProxyGroup: []map[string]interface{}{}, ProxyGroup: []map[string]interface{}{},
@ -113,131 +77,69 @@ func (c *Config) readConfig() (*RawConfig, error) {
} }
// Parse config // Parse config
func (c *Config) Parse() error { func Parse(path string) (*Config, error) {
cfg, err := c.readConfig() config := &Config{}
rawCfg, err := readConfig(path)
if err != nil { if err != nil {
return err return nil, err
} }
if err := c.parseGeneral(cfg); err != nil { general, err := parseGeneral(rawCfg)
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}
}
// SetLogLevel change log level of clash
func (c *Config) SetLogLevel(level C.LogLevel) {
c.general.LogLevel = level
c.event <- &Event{Type: "log-level", Payload: level}
}
// 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 { if err != nil {
return err return nil, err
} }
config.General = general
return c.parseRules(cfg) proxies, err := parseProxies(rawCfg)
if err != nil {
return nil, err
}
config.Proxies = proxies
rules, err := parseRules(rawCfg)
if err != nil {
return nil, err
}
config.Rules = rules
return config, nil
} }
func (c *Config) parseGeneral(cfg *RawConfig) error { func parseGeneral(cfg *rawConfig) (*General, error) {
port := cfg.Port port := cfg.Port
socksPort := cfg.SocksPort socksPort := cfg.SocksPort
redirPort := cfg.RedirPort redirPort := cfg.RedirPort
allowLan := cfg.AllowLan allowLan := cfg.AllowLan
logLevelString := cfg.LogLevel logLevelString := cfg.LogLevel
modeString := cfg.Mode modeString := cfg.Mode
externalController := cfg.ExternalController
secret := cfg.Secret
mode, exist := ModeMapping[modeString] mode, exist := T.ModeMapping[modeString]
if !exist { if !exist {
return fmt.Errorf("General.mode value invalid") return nil, fmt.Errorf("General.mode value invalid")
} }
logLevel, exist := C.LogLevelMapping[logLevelString] logLevel, exist := log.LogLevelMapping[logLevelString]
if !exist { if !exist {
return fmt.Errorf("General.log-level value invalid") return nil, fmt.Errorf("General.log-level value invalid")
} }
c.general = &General{ general := &General{
Port: port, Port: port,
SocksPort: socksPort, SocksPort: socksPort,
RedirPort: redirPort, RedirPort: redirPort,
AllowLan: allowLan, AllowLan: allowLan,
Mode: mode, Mode: mode,
LogLevel: logLevel, LogLevel: logLevel,
ExternalController: externalController,
Secret: secret,
} }
return general, nil
if restAddr := cfg.ExternalController; restAddr != "" {
c.event <- &Event{Type: "external-controller", Payload: restAddr}
c.event <- &Event{Type: "secret", Payload: cfg.Secret}
}
c.UpdateGeneral(*c.general)
return nil
} }
// UpdateGeneral dispatch update event func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) {
func (c *Config) UpdateGeneral(general General) {
c.UpdateProxy(ProxyConfig{
Port: &general.Port,
SocksPort: &general.SocksPort,
RedirPort: &general.RedirPort,
AllowLan: &general.AllowLan,
})
c.event <- &Event{Type: "mode", Payload: general.Mode}
c.event <- &Event{Type: "log-level", Payload: general.LogLevel}
}
// UpdateProxy dispatch update proxy event
func (c *Config) UpdateProxy(pc ProxyConfig) {
if pc.AllowLan != nil {
c.general.AllowLan = *pc.AllowLan
}
c.general.Port = *or(pc.Port, &c.general.Port)
if c.general.Port != 0 && (pc.AllowLan != nil || pc.Port != nil) {
c.event <- &Event{Type: "http-addr", Payload: genAddr(c.general.Port, c.general.AllowLan)}
}
c.general.SocksPort = *or(pc.SocksPort, &c.general.SocksPort)
if c.general.SocksPort != 0 && (pc.AllowLan != nil || pc.SocksPort != nil) {
c.event <- &Event{Type: "socks-addr", Payload: genAddr(c.general.SocksPort, c.general.AllowLan)}
}
c.general.RedirPort = *or(pc.RedirPort, &c.general.RedirPort)
if c.general.RedirPort != 0 && (pc.AllowLan != nil || pc.RedirPort != nil) {
c.event <- &Event{Type: "redir-addr", Payload: genAddr(c.general.RedirPort, c.general.AllowLan)}
}
}
func (c *Config) parseProxies(cfg *RawConfig) error {
proxies := make(map[string]C.Proxy) proxies := make(map[string]C.Proxy)
proxiesConfig := cfg.Proxy proxiesConfig := cfg.Proxy
groupsConfig := cfg.ProxyGroup groupsConfig := cfg.ProxyGroup
@ -251,7 +153,7 @@ func (c *Config) parseProxies(cfg *RawConfig) error {
for idx, mapping := range proxiesConfig { for idx, mapping := range proxiesConfig {
proxyType, existType := mapping["type"].(string) proxyType, existType := mapping["type"].(string)
if !existType { if !existType {
return fmt.Errorf("Proxy %d missing type", idx) return nil, fmt.Errorf("Proxy %d missing type", idx)
} }
var proxy C.Proxy var proxy C.Proxy
@ -279,15 +181,15 @@ func (c *Config) parseProxies(cfg *RawConfig) error {
} }
proxy, err = adapters.NewVmess(*vmessOption) proxy, err = adapters.NewVmess(*vmessOption)
default: default:
return fmt.Errorf("Unsupport proxy type: %s", proxyType) return nil, fmt.Errorf("Unsupport proxy type: %s", proxyType)
} }
if err != nil { if err != nil {
return fmt.Errorf("Proxy [%d]: %s", idx, err.Error()) return nil, fmt.Errorf("Proxy [%d]: %s", idx, err.Error())
} }
if _, exist := proxies[proxy.Name()]; exist { if _, exist := proxies[proxy.Name()]; exist {
return fmt.Errorf("Proxy %s is the duplicate name", proxy.Name()) return nil, fmt.Errorf("Proxy %s is the duplicate name", proxy.Name())
} }
proxies[proxy.Name()] = proxy proxies[proxy.Name()] = proxy
} }
@ -297,11 +199,11 @@ func (c *Config) parseProxies(cfg *RawConfig) error {
groupType, existType := mapping["type"].(string) groupType, existType := mapping["type"].(string)
groupName, existName := mapping["name"].(string) groupName, existName := mapping["name"].(string)
if !existType && existName { if !existType && existName {
return fmt.Errorf("ProxyGroup %d: missing type or name", idx) return nil, fmt.Errorf("ProxyGroup %d: missing type or name", idx)
} }
if _, exist := proxies[groupName]; exist { if _, exist := proxies[groupName]; exist {
return fmt.Errorf("ProxyGroup %s: the duplicate name", groupName) return nil, fmt.Errorf("ProxyGroup %s: the duplicate name", groupName)
} }
var group C.Proxy var group C.Proxy
var err error var err error
@ -315,7 +217,7 @@ func (c *Config) parseProxies(cfg *RawConfig) error {
ps, err := getProxies(proxies, urlTestOption.Proxies) ps, err := getProxies(proxies, urlTestOption.Proxies)
if err != nil { if err != nil {
return fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error()) return nil, fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error())
} }
group, err = adapters.NewURLTest(*urlTestOption, ps) group, err = adapters.NewURLTest(*urlTestOption, ps)
case "select": case "select":
@ -327,7 +229,7 @@ func (c *Config) parseProxies(cfg *RawConfig) error {
ps, err := getProxies(proxies, selectorOption.Proxies) ps, err := getProxies(proxies, selectorOption.Proxies)
if err != nil { if err != nil {
return fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error()) return nil, fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error())
} }
group, err = adapters.NewSelector(selectorOption.Name, ps) group, err = adapters.NewSelector(selectorOption.Name, ps)
case "fallback": case "fallback":
@ -339,12 +241,12 @@ func (c *Config) parseProxies(cfg *RawConfig) error {
ps, err := getProxies(proxies, fallbackOption.Proxies) ps, err := getProxies(proxies, fallbackOption.Proxies)
if err != nil { if err != nil {
return fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error()) return nil, fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error())
} }
group, err = adapters.NewFallback(*fallbackOption, ps) group, err = adapters.NewFallback(*fallbackOption, ps)
} }
if err != nil { if err != nil {
return fmt.Errorf("Proxy %s: %s", groupName, err.Error()) return nil, fmt.Errorf("Proxy %s: %s", groupName, err.Error())
} }
proxies[groupName] = group proxies[groupName] = group
} }
@ -357,7 +259,7 @@ func (c *Config) parseProxies(cfg *RawConfig) error {
proxies["GLOBAL"], _ = adapters.NewSelector("GLOBAL", ps) proxies["GLOBAL"], _ = adapters.NewSelector("GLOBAL", ps)
// close old goroutine // close old goroutine
for _, proxy := range c.proxies { for _, proxy := range proxies {
switch raw := proxy.(type) { switch raw := proxy.(type) {
case *adapters.URLTest: case *adapters.URLTest:
raw.Close() raw.Close()
@ -365,12 +267,10 @@ func (c *Config) parseProxies(cfg *RawConfig) error {
raw.Close() raw.Close()
} }
} }
c.proxies = proxies return proxies, nil
c.event <- &Event{Type: "proxies", Payload: proxies}
return nil
} }
func (c *Config) parseRules(cfg *RawConfig) error { func parseRules(cfg *rawConfig) ([]C.Rule, error) {
rules := []C.Rule{} rules := []C.Rule{}
rulesConfig := cfg.Rule rulesConfig := cfg.Rule
@ -397,55 +297,5 @@ func (c *Config) parseRules(cfg *RawConfig) error {
} }
} }
c.rules = rules return rules, nil
c.event <- &Event{Type: "rules", Payload: rules}
return nil
}
func (c *Config) handleResponseMessage() {
for elm := range c.reportCh {
event := elm.(*Event)
switch event.Type {
case "http-addr":
if event.Payload.(bool) == false {
log.Errorf("Listening HTTP proxy at %d error", c.general.Port)
c.general.Port = 0
}
case "socks-addr":
if event.Payload.(bool) == false {
log.Errorf("Listening SOCKS proxy at %d error", c.general.SocksPort)
c.general.SocksPort = 0
}
case "redir-addr":
if event.Payload.(bool) == false {
log.Errorf("Listening Redir proxy at %d error", c.general.RedirPort)
c.general.RedirPort = 0
}
}
}
}
func newConfig() *Config {
event := make(chan interface{})
reportCh := make(chan interface{})
config := &Config{
general: &General{},
proxies: make(map[string]C.Proxy),
rules: []C.Rule{},
lastUpdate: time.Now(),
event: event,
reportCh: reportCh,
observable: observable.NewObservable(event),
}
go config.handleResponseMessage()
return config
}
// Instance return singleton instance of Config
func Instance() *Config {
once.Do(func() {
config = newConfig()
})
return config
} }

View file

@ -3,6 +3,7 @@ package config
import ( import (
"archive/tar" "archive/tar"
"compress/gzip" "compress/gzip"
"fmt"
"io" "io"
"net/http" "net/http"
"os" "os"
@ -54,15 +55,15 @@ func downloadMMDB(path string) (err error) {
} }
// Init prepare necessary files // Init prepare necessary files
func Init() { func Init(dir string) error {
// initial homedir // initial homedir
if _, err := os.Stat(C.Path.HomeDir()); os.IsNotExist(err) { if _, err := os.Stat(dir); os.IsNotExist(err) {
if err := os.MkdirAll(C.Path.HomeDir(), 0777); err != nil { if err := os.MkdirAll(dir, 0777); err != nil {
log.Fatalf("Can't create config directory %s: %s", C.Path.HomeDir(), err.Error()) return fmt.Errorf("Can't create config directory %s: %s", dir, err.Error())
} }
} }
// initial config.ini // initial config.yml
if _, err := os.Stat(C.Path.Config()); os.IsNotExist(err) { if _, err := os.Stat(C.Path.Config()); os.IsNotExist(err) {
log.Info("Can't find config, create a empty file") log.Info("Can't find config, create a empty file")
os.OpenFile(C.Path.Config(), os.O_CREATE|os.O_WRONLY, 0644) os.OpenFile(C.Path.Config(), os.O_CREATE|os.O_WRONLY, 0644)
@ -73,7 +74,8 @@ func Init() {
log.Info("Can't find MMDB, start download") log.Info("Can't find MMDB, start download")
err := downloadMMDB(C.Path.MMDB()) err := downloadMMDB(C.Path.MMDB())
if err != nil { if err != nil {
log.Fatalf("Can't download MMDB: %s", err.Error()) return fmt.Errorf("Can't download MMDB: %s", err.Error())
} }
} }
return nil
} }

View file

@ -1,31 +0,0 @@
package config
type Mode int
var (
// ModeMapping is a mapping for Mode enum
ModeMapping = map[string]Mode{
"Global": Global,
"Rule": Rule,
"Direct": Direct,
}
)
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

@ -14,13 +14,6 @@ func trimArr(arr []string) (r []string) {
return return
} }
func genAddr(port int, allowLan bool) string {
if allowLan {
return fmt.Sprintf(":%d", port)
}
return fmt.Sprintf("127.0.0.1:%d", port)
}
func getProxies(mapping map[string]C.Proxy, list []string) ([]C.Proxy, error) { func getProxies(mapping map[string]C.Proxy, list []string) ([]C.Proxy, error) {
var ps []C.Proxy var ps []C.Proxy
for _, name := range list { for _, name := range list {

View file

@ -30,6 +30,7 @@ type Proxy interface {
Name() string Name() string
Type() AdapterType Type() AdapterType
Generator(metadata *Metadata) (ProxyAdapter, error) Generator(metadata *Metadata) (ProxyAdapter, error)
MarshalJSON() ([]byte, error)
} }
// AdapterType is enum of adapter type // AdapterType is enum of adapter type

View file

@ -1,10 +0,0 @@
package constant
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"`
RedirPort *int `json:"redir-port,omitempty"`
LogLevel *string `json:"log-level,omitempty"`
}

View file

@ -1,35 +0,0 @@
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,7 +0,0 @@
package constant
// ProxySignal is used to handle graceful shutdown of proxy
type ProxySignal struct {
Done chan<- struct{}
Closed <-chan struct{}
}

View file

@ -1,33 +0,0 @@
package hub
import (
"github.com/Dreamacro/clash/config"
"github.com/Dreamacro/clash/proxy"
T "github.com/Dreamacro/clash/tunnel"
)
var (
tunnel = T.Instance()
cfg = config.Instance()
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}
}

View file

@ -1,96 +0,0 @@
package hub
import (
"fmt"
"net/http"
"github.com/Dreamacro/clash/config"
C "github.com/Dreamacro/clash/constant"
"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
}
type configSchema struct {
Port int `json:"port"`
SocksPort int `json:"socket-port"`
RedirPort int `json:"redir-port"`
AllowLan bool `json:"allow-lan"`
Mode string `json:"mode"`
LogLevel string `json:"log-level"`
}
func getConfigs(w http.ResponseWriter, r *http.Request) {
general := cfg.General()
render.JSON(w, r, configSchema{
Port: general.Port,
SocksPort: general.SocksPort,
RedirPort: general.RedirPort,
AllowLan: general.AllowLan,
Mode: general.Mode.String(),
LogLevel: general.LogLevel.String(),
})
}
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 modeErr, logLevelErr error
// update mode
if general.Mode != nil {
mode, ok := config.ModeMapping[*general.Mode]
if !ok {
modeErr = fmt.Errorf("Mode error")
} else {
cfg.SetMode(mode)
}
}
// update log-level
if general.LogLevel != nil {
level, ok := C.LogLevelMapping[*general.LogLevel]
if !ok {
logLevelErr = fmt.Errorf("Log Level error")
} else {
cfg.SetLogLevel(level)
}
}
hasError, errors := formatErrors(map[string]error{
"mode": modeErr,
"log-level": logLevelErr,
})
if hasError {
w.WriteHeader(http.StatusBadRequest)
render.JSON(w, r, errors)
return
}
// update proxy
cfg.UpdateProxy(config.ProxyConfig{
AllowLan: general.AllowLan,
Port: general.Port,
SocksPort: general.SocksPort,
RedirPort: general.RedirPort,
})
w.WriteHeader(http.StatusNoContent)
}

66
hub/executor/executor.go Normal file
View file

@ -0,0 +1,66 @@
package executor
import (
"github.com/Dreamacro/clash/config"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
P "github.com/Dreamacro/clash/proxy"
T "github.com/Dreamacro/clash/tunnel"
)
// Parse config with default config path
func Parse() (*config.Config, error) {
return ParseWithPath(C.Path.Config())
}
// ParseWithPath parse config with custom config path
func ParseWithPath(path string) (*config.Config, error) {
return config.Parse(path)
}
// ApplyConfig dispatch configure to all parts
func ApplyConfig(cfg *config.Config) {
updateProxies(cfg.Proxies)
updateRules(cfg.Rules)
updateGeneral(cfg.General)
}
func GetGeneral() *config.General {
ports := P.GetPorts()
return &config.General{
Port: ports.Port,
SocksPort: ports.SocksPort,
RedirPort: ports.RedirPort,
AllowLan: P.AllowLan(),
Mode: T.Instance().Mode(),
LogLevel: log.Level(),
}
}
func updateProxies(proxies map[string]C.Proxy) {
T.Instance().UpdateProxies(proxies)
}
func updateRules(rules []C.Rule) {
T.Instance().UpdateRules(rules)
}
func updateGeneral(general *config.General) {
allowLan := general.AllowLan
P.SetAllowLan(allowLan)
if err := P.ReCreateHTTP(general.Port); err != nil {
log.Errorln("Start HTTP server error: %s", err.Error())
}
if err := P.ReCreateSocks(general.SocksPort); err != nil {
log.Errorln("Start SOCKS5 server error: %s", err.Error())
}
if err := P.ReCreateRedir(general.RedirPort); err != nil {
log.Errorln("Start Redir server error: %s", err.Error())
}
log.SetLevel(general.LogLevel)
T.Instance().SetMode(general.Mode)
}

21
hub/hub.go Normal file
View file

@ -0,0 +1,21 @@
package hub
import (
"github.com/Dreamacro/clash/hub/executor"
"github.com/Dreamacro/clash/hub/route"
)
// Parse call at the beginning of clash
func Parse() error {
cfg, err := executor.Parse()
if err != nil {
return err
}
if cfg.General.ExternalController != "" {
go route.Start(cfg.General.ExternalController, cfg.General.Secret)
}
executor.ApplyConfig(cfg)
return nil
}

View file

@ -1,217 +0,0 @@
package hub
import (
"context"
"fmt"
"net/http"
"net/url"
"strconv"
"time"
A "github.com/Dreamacro/clash/adapters/outbound"
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("/", getProxies)
r.With(parseProxyName).Get("/{name}", getProxy)
r.With(parseProxyName).Get("/{name}/delay", getProxyDelay)
r.With(parseProxyName).Put("/{name}", updateProxy)
return r
}
// When name is composed of a partial escape string, Golang does not unescape it
func parseProxyName(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
name := chi.URLParam(r, "name")
if newName, err := url.PathUnescape(name); err == nil {
name = newName
}
ctx := context.WithValue(r.Context(), contextKey("proxy name"), name)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
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"`
}
type Fallback 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(),
}
case C.Fallback:
return Fallback{
Type: t.String(),
Now: proxy.(*A.Fallback).Now(),
}
default:
return SampleProxy{
Type: proxy.Type().String(),
}
}
}
type GetProxiesResponse struct {
Proxies map[string]interface{} `json:"proxies"`
}
func getProxies(w http.ResponseWriter, r *http.Request) {
rawProxies := cfg.Proxies()
proxies := make(map[string]interface{})
for name, proxy := range rawProxies {
proxies[name] = transformProxy(proxy)
}
render.JSON(w, r, GetProxiesResponse{Proxies: proxies})
}
func getProxy(w http.ResponseWriter, r *http.Request) {
name := r.Context().Value(contextKey("proxy name")).(string)
proxies := cfg.Proxies()
proxy, exist := proxies[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 := r.Context().Value(contextKey("proxy name")).(string)
proxies := cfg.Proxies()
proxy, exist := proxies[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)
}
type GetProxyDelayRequest struct {
URL string `json:"url"`
Timeout int16 `json:"timeout"`
}
type GetProxyDelayResponse struct {
Delay int16 `json:"delay"`
}
func getProxyDelay(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()
url := query.Get("url")
timeout, err := strconv.ParseInt(query.Get("timeout"), 10, 16)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
render.JSON(w, r, Error{
Error: "Format error",
})
return
}
name := r.Context().Value(contextKey("proxy name")).(string)
proxies := cfg.Proxies()
proxy, exist := proxies[name]
if !exist {
w.WriteHeader(http.StatusNotFound)
render.JSON(w, r, Error{
Error: "Proxy not found",
})
return
}
sigCh := make(chan int16)
go func() {
t, err := A.DelayTest(proxy, url)
if err != nil {
sigCh <- 0
}
sigCh <- t
}()
select {
case <-time.After(time.Millisecond * time.Duration(timeout)):
w.WriteHeader(http.StatusRequestTimeout)
render.JSON(w, r, Error{
Error: "Proxy delay test timeout",
})
case t := <-sigCh:
if t == 0 {
w.WriteHeader(http.StatusServiceUnavailable)
render.JSON(w, r, Error{
Error: "An error occurred in the delay test",
})
} else {
render.JSON(w, r, GetProxyDelayResponse{
Delay: t,
})
}
}
}

69
hub/route/configs.go Normal file
View file

@ -0,0 +1,69 @@
package route
import (
"net/http"
"github.com/Dreamacro/clash/hub/executor"
"github.com/Dreamacro/clash/log"
T "github.com/Dreamacro/clash/tunnel"
P "github.com/Dreamacro/clash/proxy"
"github.com/go-chi/chi"
"github.com/go-chi/render"
)
func configRouter() http.Handler {
r := chi.NewRouter()
r.Get("/", getConfigs)
return r
}
type configSchema struct {
Port *int `json:"port"`
SocksPort *int `json:"socket-port"`
RedirPort *int `json:"redir-port"`
AllowLan *bool `json:"allow-lan"`
Mode *T.Mode `json:"mode"`
LogLevel *log.LogLevel `json:"log-level"`
}
func getConfigs(w http.ResponseWriter, r *http.Request) {
general := executor.GetGeneral()
render.Respond(w, r, general)
}
func pointerOrDefault (p *int, def int) int {
if p != nil {
return *p
}
return def
}
func updateConfigs(w http.ResponseWriter, r *http.Request) {
general := &configSchema{}
if err := render.DecodeJSON(r.Body, general); err != nil {
w.WriteHeader(http.StatusBadRequest)
render.Respond(w, r, ErrBadRequest)
return
}
if general.AllowLan != nil {
P.SetAllowLan(*general.AllowLan)
}
ports := P.GetPorts()
P.ReCreateHTTP(pointerOrDefault(general.Port, ports.Port))
P.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort))
P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort))
if general.Mode != nil {
T.Instance().SetMode(*general.Mode)
}
if general.LogLevel != nil {
log.SetLevel(*general.LogLevel)
}
w.WriteHeader(http.StatusNoContent)
}

12
hub/route/ctxkeys.go Normal file
View file

@ -0,0 +1,12 @@
package route
var (
CtxKeyProxyName = contextKey("proxy name")
CtxKeyProxy = contextKey("proxy")
)
type contextKey string
func (c contextKey) String() string {
return "clash context key " + string(c)
}

22
hub/route/errors.go Normal file
View file

@ -0,0 +1,22 @@
package route
var (
ErrUnauthorized = newError("Unauthorized")
ErrBadRequest = newError("Body invalid")
ErrForbidden = newError("Forbidden")
ErrNotFound = newError("Resource not found")
ErrRequestTimeout = newError("Timeout")
)
// HTTPError is custom HTTP error for API
type HTTPError struct {
Message string `json:"message"`
}
func (e *HTTPError) Error() string {
return e.Message
}
func newError(msg string) *HTTPError {
return &HTTPError{Message: msg}
}

137
hub/route/proxies.go Normal file
View file

@ -0,0 +1,137 @@
package route
import (
"context"
"fmt"
"net/http"
"net/url"
"strconv"
"time"
A "github.com/Dreamacro/clash/adapters/outbound"
C "github.com/Dreamacro/clash/constant"
T "github.com/Dreamacro/clash/tunnel"
"github.com/go-chi/chi"
"github.com/go-chi/render"
)
func proxyRouter() http.Handler {
r := chi.NewRouter()
r.Get("/", getProxies)
r.Route("/{name}", func(r chi.Router) {
r.Use(parseProxyName, findProxyByName)
r.Get("/", getProxy)
r.Get("/delay", getProxyDelay)
r.Put("/", updateProxy)
})
return r
}
// When name is composed of a partial escape string, Golang does not unescape it
func parseProxyName(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
name := chi.URLParam(r, "name")
if newName, err := url.PathUnescape(name); err == nil {
name = newName
}
ctx := context.WithValue(r.Context(), CtxKeyProxyName, name)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func findProxyByName(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
name := r.Context().Value(CtxKeyProxyName).(string)
proxies := T.Instance().Proxies()
proxy, exist := proxies[name]
if !exist {
w.WriteHeader(http.StatusNotFound)
render.Respond(w, r, ErrNotFound)
return
}
ctx := context.WithValue(r.Context(), CtxKeyProxy, proxy)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func getProxies(w http.ResponseWriter, r *http.Request) {
proxies := T.Instance().Proxies()
render.Respond(w, r, map[string]map[string]C.Proxy{
"proxies": proxies,
})
}
func getProxy(w http.ResponseWriter, r *http.Request) {
proxy := r.Context().Value(CtxKeyProxy).(C.Proxy)
render.Respond(w, r, 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.Respond(w, r, ErrBadRequest)
return
}
proxy := r.Context().Value(CtxKeyProxy).(C.Proxy)
selector, ok := proxy.(*A.Selector)
if !ok {
w.WriteHeader(http.StatusBadRequest)
render.Respond(w, r, ErrBadRequest)
return
}
if err := selector.Set(req.Name); err != nil {
w.WriteHeader(http.StatusBadRequest)
render.Respond(w, r, newError(fmt.Sprintf("Selector update error: %s", err.Error())))
return
}
w.WriteHeader(http.StatusNoContent)
}
func getProxyDelay(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()
url := query.Get("url")
timeout, err := strconv.ParseInt(query.Get("timeout"), 10, 16)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
render.Respond(w, r, ErrBadRequest)
return
}
proxy := r.Context().Value(CtxKeyProxy).(C.Proxy)
sigCh := make(chan int16)
go func() {
t, err := A.DelayTest(proxy, url)
if err != nil {
sigCh <- 0
}
sigCh <- t
}()
select {
case <-time.After(time.Millisecond * time.Duration(timeout)):
w.WriteHeader(http.StatusRequestTimeout)
render.Respond(w, r, ErrRequestTimeout)
case t := <-sigCh:
if t == 0 {
w.WriteHeader(http.StatusServiceUnavailable)
render.Respond(w, r, newError("An error occurred in the delay test"))
} else {
render.Respond(w, r, map[string]int16{
"delay": t,
})
}
}
}

View file

@ -1,8 +1,10 @@
package hub package route
import ( import (
"net/http" "net/http"
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"
) )
@ -10,7 +12,6 @@ import (
func ruleRouter() http.Handler { func ruleRouter() http.Handler {
r := chi.NewRouter() r := chi.NewRouter()
r.Get("/", getRules) r.Get("/", getRules)
r.Put("/", updateRules)
return r return r
} }
@ -20,12 +21,8 @@ type Rule struct {
Proxy string `json:"proxy"` Proxy string `json:"proxy"`
} }
type GetRulesResponse struct {
Rules []Rule `json:"rules"`
}
func getRules(w http.ResponseWriter, r *http.Request) { func getRules(w http.ResponseWriter, r *http.Request) {
rawRules := cfg.Rules() rawRules := T.Instance().Rules()
var rules []Rule var rules []Rule
for _, rule := range rawRules { for _, rule := range rawRules {
@ -37,19 +34,7 @@ func getRules(w http.ResponseWriter, r *http.Request) {
} }
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
render.JSON(w, r, GetRulesResponse{ render.Respond(w, r, map[string][]Rule{
Rules: rules, "rules": rules,
}) })
} }
func updateRules(w http.ResponseWriter, r *http.Request) {
err := cfg.UpdateRules()
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
render.JSON(w, r, Error{
Error: err.Error(),
})
return
}
w.WriteHeader(http.StatusNoContent)
}

View file

@ -1,4 +1,4 @@
package hub package route
import ( import (
"encoding/json" "encoding/json"
@ -6,44 +6,32 @@ import (
"strings" "strings"
"time" "time"
"github.com/Dreamacro/clash/config" "github.com/Dreamacro/clash/log"
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"
"github.com/go-chi/cors" "github.com/go-chi/cors"
"github.com/go-chi/render" "github.com/go-chi/render"
log "github.com/sirupsen/logrus"
) )
var secret = "" var (
serverSecret = ""
serverAddr = ""
)
type Traffic struct { type Traffic struct {
Up int64 `json:"up"` Up int64 `json:"up"`
Down int64 `json:"down"` Down int64 `json:"down"`
} }
func newHub(signal chan struct{}) { func Start(addr string, secret string) {
var addr string if serverAddr != "" {
ch := config.Instance().Subscribe() return
signal <- struct{}{}
count := 0
for {
elm := <-ch
event := elm.(*config.Event)
switch event.Type {
case "external-controller":
addr = event.Payload.(string)
count++
case "secret":
secret = event.Payload.(string)
count++
}
if count == 2 {
break
}
} }
serverAddr = addr
serverSecret = secret
r := chi.NewRouter() r := chi.NewRouter()
cors := cors.New(cors.Options{ cors := cors.New(cors.Options{
@ -59,12 +47,12 @@ func newHub(signal chan struct{}) {
r.With(jsonContentType).Get("/logs", getLogs) r.With(jsonContentType).Get("/logs", getLogs)
r.Mount("/configs", configRouter()) r.Mount("/configs", configRouter())
r.Mount("/proxies", proxyRouter()) r.Mount("/proxies", proxyRouter())
r.Mount("/rules", ruleRouter()) // r.Mount("/rules", ruleRouter())
log.Infof("RESTful API listening at: %s", addr) log.Infoln("RESTful API listening at: %s", addr)
err := http.ListenAndServe(addr, r) err := http.ListenAndServe(addr, r)
if err != nil { if err != nil {
log.Errorf("External controller error: %s", err.Error()) log.Errorln("External controller error: %s", err.Error())
} }
} }
@ -81,18 +69,16 @@ func authentication(next http.Handler) http.Handler {
header := r.Header.Get("Authorization") header := r.Header.Get("Authorization")
text := strings.SplitN(header, " ", 2) text := strings.SplitN(header, " ", 2)
if secret == "" { if serverSecret == "" {
next.ServeHTTP(w, r) next.ServeHTTP(w, r)
return return
} }
hasUnvalidHeader := text[0] != "Bearer" hasUnvalidHeader := text[0] != "Bearer"
hasUnvalidSecret := len(text) == 2 && text[1] != secret hasUnvalidSecret := len(text) == 2 && text[1] != serverSecret
if hasUnvalidHeader || hasUnvalidSecret { if hasUnvalidHeader || hasUnvalidSecret {
w.WriteHeader(http.StatusUnauthorized) w.WriteHeader(http.StatusUnauthorized)
render.JSON(w, r, Error{ render.Respond(w, r, ErrUnauthorized)
Error: "Authentication failed",
})
return return
} }
next.ServeHTTP(w, r) next.ServeHTTP(w, r)
@ -100,17 +86,11 @@ func authentication(next http.Handler) http.Handler {
return http.HandlerFunc(fn) return http.HandlerFunc(fn)
} }
type contextKey string
func (c contextKey) String() string {
return "clash context key " + string(c)
}
func traffic(w http.ResponseWriter, r *http.Request) { func traffic(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
tick := time.NewTicker(time.Second) tick := time.NewTicker(time.Second)
t := tunnel.Traffic() t := T.Instance().Traffic()
for range tick.C { for range tick.C {
up, down := t.Now() up, down := t.Now()
if err := json.NewEncoder(w).Encode(Traffic{ if err := json.NewEncoder(w).Encode(Traffic{
@ -134,29 +114,18 @@ func getLogs(w http.ResponseWriter, r *http.Request) {
levelText = "info" levelText = "info"
} }
level, ok := C.LogLevelMapping[levelText] level, ok := log.LogLevelMapping[levelText]
if !ok { if !ok {
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
render.JSON(w, r, Error{ render.Respond(w, r, ErrBadRequest)
Error: "Level error",
})
return return
} }
src := tunnel.Log() sub := log.Subscribe()
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) render.Status(r, http.StatusOK)
for elm := range sub { for elm := range sub {
log := elm.(T.Log) log := elm.(*log.Event)
if log.LogLevel > level { if log.LogLevel < level {
continue continue
} }
@ -169,10 +138,3 @@ 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
}

76
log/level.go Normal file
View file

@ -0,0 +1,76 @@
package log
import (
"encoding/json"
"errors"
yaml "gopkg.in/yaml.v2"
)
var (
// LogLevelMapping is a mapping for LogLevel enum
LogLevelMapping = map[string]LogLevel{
"error": ERROR,
"warning": WARNING,
"info": INFO,
"debug": DEBUG,
}
)
const (
DEBUG LogLevel = iota
INFO
WARNING
ERROR
)
type LogLevel int
// UnmarshalYAML unserialize Mode with yaml
func (l *LogLevel) UnmarshalYAML(data []byte) error {
var tp string
yaml.Unmarshal(data, &tp)
level, exist := LogLevelMapping[tp]
if !exist {
return errors.New("invalid mode")
}
*l = level
return nil
}
// MarshalYAML serialize Mode with yaml
func (l LogLevel) MarshalYAML() ([]byte, error) {
return yaml.Marshal(l.String())
}
// UnmarshalJSON unserialize Mode with json
func (l *LogLevel) UnmarshalJSON(data []byte) error {
var tp string
json.Unmarshal(data, tp)
level, exist := LogLevelMapping[tp]
if !exist {
return errors.New("invalid mode")
}
*l = level
return nil
}
// MarshalJSON serialize Mode with json
func (l LogLevel) MarshalJSON() ([]byte, error) {
return json.Marshal(l.String())
}
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"
}
}

85
log/log.go Normal file
View file

@ -0,0 +1,85 @@
package log
import (
"fmt"
"github.com/Dreamacro/clash/common/observable"
log "github.com/sirupsen/logrus"
)
var (
logCh = make(chan interface{})
source = observable.NewObservable(logCh)
level = INFO
)
type Event struct {
LogLevel LogLevel
Payload string
}
func (e *Event) Type() string {
return e.LogLevel.String()
}
func Infoln(format string, v ...interface{}) {
event := newLog(INFO, format, v...)
logCh <- event
print(event)
}
func Warnln(format string, v ...interface{}) {
event := newLog(WARNING, format, v...)
logCh <- event
print(event)
}
func Errorln(format string, v ...interface{}) {
event := newLog(ERROR, format, v...)
logCh <- event
print(event)
}
func Debugln(format string, v ...interface{}) {
event := newLog(DEBUG, format, v...)
logCh <- event
print(event)
}
func Subscribe() observable.Subscription {
sub, _ := source.Subscribe()
return sub
}
func Level() LogLevel {
return level
}
func SetLevel(newLevel LogLevel) {
level = newLevel
}
func print(data *Event) {
if data.LogLevel < level {
return
}
switch data.LogLevel {
case INFO:
log.Infoln(data.Payload)
case WARNING:
log.Warnln(data.Payload)
case ERROR:
log.Errorln(data.Payload)
case DEBUG:
log.Debugln(data.Payload)
}
}
func newLog(logLevel LogLevel, format string, v ...interface{}) *Event {
return &Event{
LogLevel: logLevel,
Payload: fmt.Sprintf(format, v...),
}
}

14
main.go
View file

@ -10,8 +10,6 @@ import (
"github.com/Dreamacro/clash/config" "github.com/Dreamacro/clash/config"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/hub" "github.com/Dreamacro/clash/hub"
"github.com/Dreamacro/clash/proxy"
"github.com/Dreamacro/clash/tunnel"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
@ -26,10 +24,6 @@ func init() {
} }
func main() { func main() {
tunnel.Instance().Run()
proxy.Instance().Run()
hub.Run()
if homedir != "" { if homedir != "" {
if !filepath.IsAbs(homedir) { if !filepath.IsAbs(homedir) {
currentDir, _ := os.Getwd() currentDir, _ := os.Getwd()
@ -38,9 +32,11 @@ func main() {
C.SetHomeDir(homedir) C.SetHomeDir(homedir)
} }
config.Init() if err := config.Init(C.Path.HomeDir()); err != nil {
err := config.Instance().Parse() log.Fatalf("Initial configuration directory error: %s", err.Error())
if err != nil { }
if err := hub.Parse(); err != nil {
log.Fatalf("Parse config error: %s", err.Error()) log.Fatalf("Parse config error: %s", err.Error())
} }

View file

@ -6,31 +6,25 @@ import (
"net/http" "net/http"
"github.com/Dreamacro/clash/adapters/inbound" "github.com/Dreamacro/clash/adapters/inbound"
C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/tunnel" "github.com/Dreamacro/clash/tunnel"
log "github.com/sirupsen/logrus"
) )
var ( var (
tun = tunnel.Instance() tun = tunnel.Instance()
) )
func NewHttpProxy(addr string) (*C.ProxySignal, error) { func NewHttpProxy(addr string) (chan<- struct{}, <-chan struct{}, error) {
l, err := net.Listen("tcp", addr) l, err := net.Listen("tcp", addr)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
done := make(chan struct{}) done := make(chan struct{})
closed := make(chan struct{}) closed := make(chan struct{})
signal := &C.ProxySignal{
Done: done,
Closed: closed,
}
go func() { go func() {
log.Infof("HTTP proxy listening at: %s", addr) log.Infoln("HTTP proxy listening at: %s", addr)
for { for {
c, err := l.Accept() c, err := l.Accept()
if err != nil { if err != nil {
@ -45,12 +39,11 @@ func NewHttpProxy(addr string) (*C.ProxySignal, error) {
go func() { go func() {
<-done <-done
close(done)
l.Close() l.Close()
closed <- struct{}{} closed <- struct{}{}
}() }()
return signal, nil return done, closed, nil
} }
func handleConn(conn net.Conn) { func handleConn(conn net.Conn) {

View file

@ -1,116 +1,166 @@
package proxy package proxy
import ( import (
"sync" "fmt"
"net"
"strconv"
"github.com/Dreamacro/clash/config"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/proxy/http" "github.com/Dreamacro/clash/proxy/http"
"github.com/Dreamacro/clash/proxy/redir" "github.com/Dreamacro/clash/proxy/redir"
"github.com/Dreamacro/clash/proxy/socks" "github.com/Dreamacro/clash/proxy/socks"
) )
var ( var (
listener *Listener allowLan = false
once sync.Once
socksListener *listener
httpListener *listener
redirListener *listener
) )
type Listener struct { type listener struct {
// signal for update Address string
httpSignal *C.ProxySignal Done chan<- struct{}
socksSignal *C.ProxySignal Closed <-chan struct{}
redirSignal *C.ProxySignal
} }
func (l *Listener) updateHTTP(addr string) error { type Ports struct {
if l.httpSignal != nil { Port int `json:"port"`
signal := l.httpSignal SocksPort int `json:"socks-port"`
signal.Done <- struct{}{} RedirPort int `json:"redir-port"`
<-signal.Closed
l.httpSignal = nil
}
signal, err := http.NewHttpProxy(addr)
if err != nil {
return err
}
l.httpSignal = signal
return nil
} }
func (l *Listener) updateSocks(addr string) error { func AllowLan() bool {
if l.socksSignal != nil { return allowLan
signal := l.socksSignal
signal.Done <- struct{}{}
<-signal.Closed
l.socksSignal = nil
}
signal, err := socks.NewSocksProxy(addr)
if err != nil {
return err
}
l.socksSignal = signal
return nil
} }
func (l *Listener) updateRedir(addr string) error { func SetAllowLan(al bool) {
if l.redirSignal != nil { allowLan = al
signal := l.redirSignal
signal.Done <- struct{}{}
<-signal.Closed
l.redirSignal = nil
}
signal, err := redir.NewRedirProxy(addr)
if err != nil {
return err
}
l.redirSignal = signal
return nil
} }
func (l *Listener) process(signal chan<- struct{}) { func ReCreateHTTP(port int) error {
sub := config.Instance().Subscribe() addr := genAddr(port, allowLan)
signal <- struct{}{}
reportCH := config.Instance().Report() if httpListener != nil {
for elm := range sub { if httpListener.Address == addr {
event := elm.(*config.Event) return nil
switch event.Type {
case "http-addr":
addr := event.Payload.(string)
err := l.updateHTTP(addr)
reportCH <- &config.Event{Type: "http-addr", Payload: err == nil}
case "socks-addr":
addr := event.Payload.(string)
err := l.updateSocks(addr)
reportCH <- &config.Event{Type: "socks-addr", Payload: err == nil}
case "redir-addr":
addr := event.Payload.(string)
err := l.updateRedir(addr)
reportCH <- &config.Event{Type: "redir-addr", Payload: err == nil}
} }
httpListener.Done <- struct{}{}
<-httpListener.Closed
httpListener = nil
} }
if portIsZero(addr) {
return nil
}
done, closed, err := http.NewHttpProxy(addr)
if err != nil {
return err
}
httpListener = &listener{
Address: addr,
Done: done,
Closed: closed,
}
return nil
} }
// Run ensure config monitoring func ReCreateSocks(port int) error {
func (l *Listener) Run() { addr := genAddr(port, allowLan)
signal := make(chan struct{})
go l.process(signal) if socksListener != nil {
<-signal if socksListener.Address == addr {
return nil
}
socksListener.Done <- struct{}{}
<-socksListener.Closed
socksListener = nil
}
if portIsZero(addr) {
return nil
}
done, closed, err := socks.NewSocksProxy(addr)
if err != nil {
return err
}
socksListener = &listener{
Address: addr,
Done: done,
Closed: closed,
}
return nil
} }
func newListener() *Listener { func ReCreateRedir(port int) error {
return &Listener{} addr := genAddr(port, allowLan)
if redirListener != nil {
if redirListener.Address == addr {
return nil
}
redirListener.Done <- struct{}{}
<-redirListener.Closed
redirListener = nil
}
if portIsZero(addr) {
return nil
}
done, closed, err := redir.NewRedirProxy(addr)
if err != nil {
return err
}
redirListener = &listener{
Address: addr,
Done: done,
Closed: closed,
}
return nil
} }
// Instance return singleton instance of Listener // GetPorts return the ports of proxy servers
func Instance() *Listener { func GetPorts() *Ports {
once.Do(func() { ports := &Ports{}
listener = newListener()
}) if httpListener != nil {
return listener _, portStr, _ := net.SplitHostPort(httpListener.Address)
port, _ := strconv.Atoi(portStr)
ports.Port = port
}
if socksListener != nil {
_, portStr, _ := net.SplitHostPort(socksListener.Address)
port, _ := strconv.Atoi(portStr)
ports.SocksPort = port
}
if redirListener != nil {
_, portStr, _ := net.SplitHostPort(redirListener.Address)
port, _ := strconv.Atoi(portStr)
ports.RedirPort = port
}
return ports
}
func portIsZero(addr string) bool {
_, port, err := net.SplitHostPort(addr)
if port == "0" || port == "" || err != nil {
return true
}
return false
}
func genAddr(port int, allowLan bool) string {
if allowLan {
return fmt.Sprintf(":%d", port)
}
return fmt.Sprintf("127.0.0.1:%d", port)
} }

View file

@ -4,7 +4,6 @@ import (
"net" "net"
"github.com/Dreamacro/clash/adapters/inbound" "github.com/Dreamacro/clash/adapters/inbound"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/tunnel" "github.com/Dreamacro/clash/tunnel"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@ -14,18 +13,14 @@ var (
tun = tunnel.Instance() tun = tunnel.Instance()
) )
func NewRedirProxy(addr string) (*C.ProxySignal, error) { func NewRedirProxy(addr string) (chan<- struct{}, <-chan struct{}, error) {
l, err := net.Listen("tcp", addr) l, err := net.Listen("tcp", addr)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
done := make(chan struct{}) done := make(chan struct{})
closed := make(chan struct{}) closed := make(chan struct{})
signal := &C.ProxySignal{
Done: done,
Closed: closed,
}
go func() { go func() {
log.Infof("Redir proxy listening at: %s", addr) log.Infof("Redir proxy listening at: %s", addr)
@ -43,12 +38,11 @@ func NewRedirProxy(addr string) (*C.ProxySignal, error) {
go func() { go func() {
<-done <-done
close(done)
l.Close() l.Close()
closed <- struct{}{} closed <- struct{}{}
}() }()
return signal, nil return done, closed, nil
} }
func handleRedir(conn net.Conn) { func handleRedir(conn net.Conn) {

View file

@ -4,7 +4,6 @@ import (
"net" "net"
"github.com/Dreamacro/clash/adapters/inbound" "github.com/Dreamacro/clash/adapters/inbound"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/tunnel" "github.com/Dreamacro/clash/tunnel"
"github.com/Dreamacro/go-shadowsocks2/socks" "github.com/Dreamacro/go-shadowsocks2/socks"
@ -15,18 +14,14 @@ var (
tun = tunnel.Instance() tun = tunnel.Instance()
) )
func NewSocksProxy(addr string) (*C.ProxySignal, error) { func NewSocksProxy(addr string) (chan<- struct{}, <-chan struct{}, error) {
l, err := net.Listen("tcp", addr) l, err := net.Listen("tcp", addr)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
done := make(chan struct{}) done := make(chan struct{})
closed := make(chan struct{}) closed := make(chan struct{})
signal := &C.ProxySignal{
Done: done,
Closed: closed,
}
go func() { go func() {
log.Infof("SOCKS proxy listening at: %s", addr) log.Infof("SOCKS proxy listening at: %s", addr)
@ -44,12 +39,11 @@ func NewSocksProxy(addr string) (*C.ProxySignal, error) {
go func() { go func() {
<-done <-done
close(done)
l.Close() l.Close()
closed <- struct{}{} closed <- struct{}{}
}() }()
return signal, nil return done, closed, nil
} }
func handleSocks(conn net.Conn) { func handleSocks(conn net.Conn) {

View file

@ -1,51 +0,0 @@
package tunnel
import (
"fmt"
C "github.com/Dreamacro/clash/constant"
log "github.com/sirupsen/logrus"
)
type Log struct {
LogLevel C.LogLevel
Payload string
}
func (l *Log) Type() string {
return l.LogLevel.String()
}
func print(data Log) {
switch data.LogLevel {
case C.INFO:
log.Infoln(data.Payload)
case C.WARNING:
log.Warnln(data.Payload)
case C.ERROR:
log.Errorln(data.Payload)
case C.DEBUG:
log.Debugln(data.Payload)
}
}
func (t *Tunnel) subscribeLogs() {
sub, err := t.observable.Subscribe()
if err != nil {
log.Fatalf("Can't subscribe tunnel log: %s", err.Error())
}
for elm := range sub {
data := elm.(Log)
if data.LogLevel <= t.logLevel {
print(data)
}
}
}
func newLog(logLevel C.LogLevel, format string, v ...interface{}) Log {
return Log{
LogLevel: logLevel,
Payload: fmt.Sprintf(format, v...),
}
}

53
tunnel/mode.go Normal file
View file

@ -0,0 +1,53 @@
package tunnel
import (
"encoding/json"
"errors"
)
type Mode int
var (
// ModeMapping is a mapping for Mode enum
ModeMapping = map[string]Mode{
"Global": Global,
"Rule": Rule,
"Direct": Direct,
}
)
const (
Global Mode = iota
Rule
Direct
)
// UnmarshalJSON unserialize Mode
func (m *Mode) UnmarshalJSON(data []byte) error {
var tp string
json.Unmarshal(data, tp)
mode, exist := ModeMapping[tp]
if !exist {
return errors.New("invalid mode")
}
*m = mode
return nil
}
// MarshalJSON serialize Mode
func (m Mode) MarshalJSON() ([]byte, error) {
return json.Marshal(m.String())
}
func (m Mode) String() string {
switch m {
case Global:
return "Global"
case Rule:
return "Rule"
case Direct:
return "Direct"
default:
return "Unknow"
}
}

View file

@ -5,9 +5,8 @@ import (
"time" "time"
InboundAdapter "github.com/Dreamacro/clash/adapters/inbound" InboundAdapter "github.com/Dreamacro/clash/adapters/inbound"
"github.com/Dreamacro/clash/common/observable"
cfg "github.com/Dreamacro/clash/config"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
"gopkg.in/eapache/channels.v1" "gopkg.in/eapache/channels.v1"
) )
@ -26,12 +25,7 @@ type Tunnel struct {
traffic *C.Traffic traffic *C.Traffic
// Outbound Rule // Outbound Rule
mode cfg.Mode mode Mode
// Log
logCh chan interface{}
observable *observable.Observable
logLevel C.LogLevel
} }
// Add request to queue // Add request to queue
@ -44,33 +38,38 @@ func (t *Tunnel) Traffic() *C.Traffic {
return t.traffic return t.traffic
} }
// Log return clash log stream // Rules return all rules
func (t *Tunnel) Log() *observable.Observable { func (t *Tunnel) Rules() []C.Rule {
return t.observable return t.rules
} }
func (t *Tunnel) configMonitor(signal chan<- struct{}) { // UpdateRules handle update rules
sub := cfg.Instance().Subscribe() func (t *Tunnel) UpdateRules(rules []C.Rule) {
signal <- struct{}{} t.configLock.Lock()
for elm := range sub { t.rules = rules
event := elm.(*cfg.Event) t.configLock.Unlock()
switch event.Type { }
case "proxies":
proxies := event.Payload.(map[string]C.Proxy) // Proxies return all proxies
t.configLock.Lock() func (t *Tunnel) Proxies() map[string]C.Proxy {
t.proxies = proxies return t.proxies
t.configLock.Unlock() }
case "rules":
rules := event.Payload.([]C.Rule) // UpdateProxies handle update proxies
t.configLock.Lock() func (t *Tunnel) UpdateProxies(proxies map[string]C.Proxy) {
t.rules = rules t.configLock.Lock()
t.configLock.Unlock() t.proxies = proxies
case "mode": t.configLock.Unlock()
t.mode = event.Payload.(cfg.Mode) }
case "log-level":
t.logLevel = event.Payload.(C.LogLevel) // Mode return current mode
} func (t *Tunnel) Mode() Mode {
} return t.mode
}
// SetMode change the mode of tunnel
func (t *Tunnel) SetMode(mode Mode) {
t.mode = mode
} }
func (t *Tunnel) process() { func (t *Tunnel) process() {
@ -88,9 +87,9 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) {
var proxy C.Proxy var proxy C.Proxy
switch t.mode { switch t.mode {
case cfg.Direct: case Direct:
proxy = t.proxies["DIRECT"] proxy = t.proxies["DIRECT"]
case cfg.Global: case Global:
proxy = t.proxies["GLOBAL"] proxy = t.proxies["GLOBAL"]
// Rule // Rule
default: default:
@ -98,7 +97,7 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) {
} }
remoConn, err := proxy.Generator(metadata) remoConn, err := proxy.Generator(metadata)
if err != nil { if err != nil {
t.logCh <- newLog(C.WARNING, "Proxy connect error: %s", err.Error()) log.Warnln("Proxy connect error: %s", err.Error())
return return
} }
defer remoConn.Close() defer remoConn.Close()
@ -121,34 +120,21 @@ func (t *Tunnel) match(metadata *C.Metadata) C.Proxy {
if !ok { if !ok {
continue continue
} }
t.logCh <- newLog(C.INFO, "%v match %s using %s", metadata.String(), rule.RuleType().String(), rule.Adapter()) log.Infoln("%v match %s using %s", metadata.String(), rule.RuleType().String(), rule.Adapter())
return a return a
} }
} }
t.logCh <- newLog(C.INFO, "%v doesn't match any rule using DIRECT", metadata.String()) log.Infoln("%v doesn't match any rule using DIRECT", metadata.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{})
return &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),
logCh: logCh,
configLock: &sync.RWMutex{}, configLock: &sync.RWMutex{},
traffic: C.NewTraffic(time.Second), traffic: C.NewTraffic(time.Second),
mode: cfg.Rule, mode: Rule,
logLevel: C.INFO,
} }
} }
@ -156,6 +142,7 @@ func newTunnel() *Tunnel {
func Instance() *Tunnel { func Instance() *Tunnel {
once.Do(func() { once.Do(func() {
tunnel = newTunnel() tunnel = newTunnel()
go tunnel.process()
}) })
return tunnel return tunnel
} }