Improve: config convergent and add log-level
This commit is contained in:
parent
7357d2d0c2
commit
8389150318
31 changed files with 757 additions and 484 deletions
32
adapters/local/http.go
Normal file
32
adapters/local/http.go
Normal 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
|
||||||
|
}
|
|
@ -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
32
adapters/local/socks.go
Normal 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
66
adapters/local/util.go
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
|
@ -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{}
|
||||||
}
|
}
|
|
@ -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() {}
|
||||||
|
|
|
@ -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()
|
||||||
|
@ -37,7 +31,6 @@ 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)
|
||||||
|
@ -70,7 +63,6 @@ func NewShadowSocks(name string, ssURL string, traffic *C.Traffic) (*ShadowSocks
|
||||||
server: server,
|
server: server,
|
||||||
name: name,
|
name: name,
|
||||||
cipher: ciph,
|
cipher: ciph,
|
||||||
traffic: traffic,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
296
config/config.go
Normal file
296
config/config.go
Normal 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
17
config/general.go
Normal 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
|
||||||
|
}
|
|
@ -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
|
|
@ -1,4 +1,4 @@
|
||||||
package tunnel
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
35
constant/log.go
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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{
|
||||||
|
|
|
@ -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
22
main.go
|
@ -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)
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
67
tunnel/connection.go
Normal 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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
if data.LogLevel <= t.logLevel {
|
||||||
print(data)
|
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...),
|
||||||
|
|
221
tunnel/tunnel.go
221
tunnel/tunnel.go
|
@ -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) {
|
|
||||||
cfg, err := C.GetConfig()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// empty proxies and rules
|
|
||||||
proxies := make(map[string]C.Proxy)
|
|
||||||
rules := []C.Rule{}
|
|
||||||
|
|
||||||
proxiesConfig := cfg.Section("Proxy")
|
|
||||||
rulesConfig := cfg.Section("Rule")
|
|
||||||
groupsConfig := cfg.Section("Proxy Group")
|
|
||||||
|
|
||||||
// parse proxy
|
|
||||||
for _, key := range proxiesConfig.Keys() {
|
|
||||||
proxy := key.Strings(",")
|
|
||||||
if len(proxy) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch proxy[0] {
|
|
||||||
// ss, server, port, cipter, password
|
|
||||||
case "ss":
|
|
||||||
if len(proxy) < 5 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
ssURL := fmt.Sprintf("ss://%s:%s@%s:%s", proxy[3], proxy[4], proxy[1], proxy[2])
|
|
||||||
ss, err := adapters.NewShadowSocks(key.Name(), ssURL, t.traffic)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
proxies[key.Name()] = ss
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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()
|
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.proxies = proxies
|
||||||
|
t.configLock.Unlock()
|
||||||
|
case "rules":
|
||||||
|
rules := event.Payload.([]C.Rule)
|
||||||
|
t.configLock.Lock()
|
||||||
t.rules = rules
|
t.rules = rules
|
||||||
t.selector = s
|
t.configLock.Unlock()
|
||||||
|
case "mode":
|
||||||
return nil
|
t.mode = event.Payload.(cfg.Mode)
|
||||||
|
case "log-level":
|
||||||
|
t.logLevel = event.Payload.(C.LogLevel)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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()
|
||||||
})
|
})
|
||||||
|
|
|
@ -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}
|
||||||
}
|
}
|
Loading…
Reference in a new issue