Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
49635eab6c | |||
a46041b81c | |||
a6bbc67afb | |||
afc4644dd1 | |||
1607d3253f | |||
34c8655974 | |||
5e4b35e03a | |||
fa9077969c |
10
README.md
10
README.md
@ -5,7 +5,7 @@
|
|||||||
<br>
|
<br>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<h4 align="center">A rule based tunnel in Go.</h4>
|
<h4 align="center">A rule-based tunnel in Go.</h4>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://travis-ci.org/Dreamacro/clash">
|
<a href="https://travis-ci.org/Dreamacro/clash">
|
||||||
@ -96,6 +96,10 @@ log-level: info
|
|||||||
# A RESTful API for clash
|
# A RESTful API for clash
|
||||||
external-controller: 127.0.0.1:9090
|
external-controller: 127.0.0.1:9090
|
||||||
|
|
||||||
|
# you can put the static web resource (such as clash-dashboard) to a directory, and clash would serve in `${API}/ui`
|
||||||
|
# input is a relative path to the configuration directory or an absolute path
|
||||||
|
# external-ui: folder
|
||||||
|
|
||||||
# Secret for RESTful API (Optional)
|
# Secret for RESTful API (Optional)
|
||||||
# secret: ""
|
# secret: ""
|
||||||
|
|
||||||
@ -126,8 +130,8 @@ Proxy:
|
|||||||
- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, tls: true }
|
- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, tls: true }
|
||||||
# with tls and skip-cert-verify
|
# with tls and skip-cert-verify
|
||||||
- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, tls: true, skip-cert-verify: true }
|
- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, tls: true, skip-cert-verify: true }
|
||||||
# with ws
|
# with ws-path and ws-headers
|
||||||
- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, network: ws, ws-path: /path }
|
- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, network: ws, ws-path: /path, ws-headers: { Host: v2ray.com } }
|
||||||
# with ws + tls
|
# with ws + tls
|
||||||
- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, network: ws, ws-path: /path, tls: true }
|
- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, network: ws, ws-path: /path, tls: true }
|
||||||
|
|
||||||
|
@ -32,16 +32,17 @@ type Vmess struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type VmessOption struct {
|
type VmessOption struct {
|
||||||
Name string `proxy:"name"`
|
Name string `proxy:"name"`
|
||||||
Server string `proxy:"server"`
|
Server string `proxy:"server"`
|
||||||
Port int `proxy:"port"`
|
Port int `proxy:"port"`
|
||||||
UUID string `proxy:"uuid"`
|
UUID string `proxy:"uuid"`
|
||||||
AlterID int `proxy:"alterId"`
|
AlterID int `proxy:"alterId"`
|
||||||
Cipher string `proxy:"cipher"`
|
Cipher string `proxy:"cipher"`
|
||||||
TLS bool `proxy:"tls,omitempty"`
|
TLS bool `proxy:"tls,omitempty"`
|
||||||
Network string `proxy:"network,omitempty"`
|
Network string `proxy:"network,omitempty"`
|
||||||
WSPath string `proxy:"ws-path,omitempty"`
|
WSPath string `proxy:"ws-path,omitempty"`
|
||||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
|
||||||
|
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Vmess) Name() string {
|
func (v *Vmess) Name() string {
|
||||||
@ -71,16 +72,17 @@ func (v *Vmess) MarshalJSON() ([]byte, error) {
|
|||||||
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{
|
||||||
UUID: option.UUID,
|
UUID: option.UUID,
|
||||||
AlterID: uint16(option.AlterID),
|
AlterID: uint16(option.AlterID),
|
||||||
Security: security,
|
Security: security,
|
||||||
TLS: option.TLS,
|
TLS: option.TLS,
|
||||||
HostName: option.Server,
|
HostName: option.Server,
|
||||||
Port: strconv.Itoa(option.Port),
|
Port: strconv.Itoa(option.Port),
|
||||||
NetWork: option.Network,
|
NetWork: option.Network,
|
||||||
WebSocketPath: option.WSPath,
|
WebSocketPath: option.WSPath,
|
||||||
SkipCertVerify: option.SkipCertVerify,
|
WebSocketHeaders: option.WSHeaders,
|
||||||
SessionCacahe: getClientSessionCache(),
|
SkipCertVerify: option.SkipCertVerify,
|
||||||
|
SessionCacahe: getClientSessionCache(),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package structure
|
package structure
|
||||||
|
|
||||||
|
// references: https://github.com/mitchellh/mapstructure
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
@ -70,6 +72,8 @@ func (d *Decoder) decode(name string, data interface{}, val reflect.Value) error
|
|||||||
return d.decodeBool(name, data, val)
|
return d.decodeBool(name, data, val)
|
||||||
case reflect.Slice:
|
case reflect.Slice:
|
||||||
return d.decodeSlice(name, data, val)
|
return d.decodeSlice(name, data, val)
|
||||||
|
case reflect.Map:
|
||||||
|
return d.decodeMap(name, data, val)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("type %s not support", val.Kind().String())
|
return fmt.Errorf("type %s not support", val.Kind().String())
|
||||||
}
|
}
|
||||||
@ -158,3 +162,70 @@ func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value)
|
|||||||
val.Set(valSlice)
|
val.Set(valSlice)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Decoder) decodeMap(name string, data interface{}, val reflect.Value) error {
|
||||||
|
valType := val.Type()
|
||||||
|
valKeyType := valType.Key()
|
||||||
|
valElemType := valType.Elem()
|
||||||
|
|
||||||
|
valMap := val
|
||||||
|
|
||||||
|
if valMap.IsNil() {
|
||||||
|
mapType := reflect.MapOf(valKeyType, valElemType)
|
||||||
|
valMap = reflect.MakeMap(mapType)
|
||||||
|
}
|
||||||
|
|
||||||
|
dataVal := reflect.Indirect(reflect.ValueOf(data))
|
||||||
|
if dataVal.Kind() != reflect.Map {
|
||||||
|
return fmt.Errorf("'%s' expected a map, got '%s'", name, dataVal.Kind())
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.decodeMapFromMap(name, dataVal, val, valMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Decoder) decodeMapFromMap(name string, dataVal reflect.Value, val reflect.Value, valMap reflect.Value) error {
|
||||||
|
valType := val.Type()
|
||||||
|
valKeyType := valType.Key()
|
||||||
|
valElemType := valType.Elem()
|
||||||
|
|
||||||
|
errors := make([]string, 0)
|
||||||
|
|
||||||
|
if dataVal.Len() == 0 {
|
||||||
|
if dataVal.IsNil() {
|
||||||
|
if !val.IsNil() {
|
||||||
|
val.Set(dataVal)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val.Set(valMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, k := range dataVal.MapKeys() {
|
||||||
|
fieldName := fmt.Sprintf("%s[%s]", name, k)
|
||||||
|
|
||||||
|
currentKey := reflect.Indirect(reflect.New(valKeyType))
|
||||||
|
if err := d.decode(fieldName, k.Interface(), currentKey); err != nil {
|
||||||
|
errors = append(errors, err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
v := dataVal.MapIndex(k).Interface()
|
||||||
|
currentVal := reflect.Indirect(reflect.New(valElemType))
|
||||||
|
if err := d.decode(fieldName, v, currentVal); err != nil {
|
||||||
|
errors = append(errors, err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
valMap.SetMapIndex(currentKey, currentVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
val.Set(valMap)
|
||||||
|
|
||||||
|
if len(errors) > 0 {
|
||||||
|
return fmt.Errorf(strings.Join(errors, ","))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -75,16 +75,17 @@ type Client struct {
|
|||||||
|
|
||||||
// Config of vmess
|
// Config of vmess
|
||||||
type Config struct {
|
type Config struct {
|
||||||
UUID string
|
UUID string
|
||||||
AlterID uint16
|
AlterID uint16
|
||||||
Security string
|
Security string
|
||||||
TLS bool
|
TLS bool
|
||||||
HostName string
|
HostName string
|
||||||
Port string
|
Port string
|
||||||
NetWork string
|
NetWork string
|
||||||
WebSocketPath string
|
WebSocketPath string
|
||||||
SkipCertVerify bool
|
WebSocketHeaders map[string]string
|
||||||
SessionCacahe tls.ClientSessionCache
|
SkipCertVerify bool
|
||||||
|
SessionCacahe tls.ClientSessionCache
|
||||||
}
|
}
|
||||||
|
|
||||||
// New return a Conn with net.Conn and DstAddr
|
// New return a Conn with net.Conn and DstAddr
|
||||||
@ -149,6 +150,7 @@ func NewClient(config Config) (*Client, error) {
|
|||||||
wsConfig = &websocketConfig{
|
wsConfig = &websocketConfig{
|
||||||
host: host,
|
host: host,
|
||||||
path: config.WebSocketPath,
|
path: config.WebSocketPath,
|
||||||
|
headers: config.WebSocketHeaders,
|
||||||
tls: config.TLS,
|
tls: config.TLS,
|
||||||
tlsConfig: tlsConfig,
|
tlsConfig: tlsConfig,
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -21,6 +22,7 @@ type websocketConn struct {
|
|||||||
type websocketConfig struct {
|
type websocketConfig struct {
|
||||||
host string
|
host string
|
||||||
path string
|
path string
|
||||||
|
headers map[string]string
|
||||||
tls bool
|
tls bool
|
||||||
tlsConfig *tls.Config
|
tlsConfig *tls.Config
|
||||||
}
|
}
|
||||||
@ -127,7 +129,14 @@ func newWebsocketConn(conn net.Conn, c *websocketConfig) (net.Conn, error) {
|
|||||||
Path: c.path,
|
Path: c.path,
|
||||||
}
|
}
|
||||||
|
|
||||||
wsConn, resp, err := dialer.Dial(uri.String(), nil)
|
headers := http.Header{}
|
||||||
|
if c.headers != nil {
|
||||||
|
for k, v := range c.headers {
|
||||||
|
headers.Set(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wsConn, resp, err := dialer.Dial(uri.String(), headers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var reason string
|
var reason string
|
||||||
if resp != nil {
|
if resp != nil {
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
adapters "github.com/Dreamacro/clash/adapters/outbound"
|
adapters "github.com/Dreamacro/clash/adapters/outbound"
|
||||||
@ -27,8 +28,9 @@ type General struct {
|
|||||||
AllowLan bool `json:"allow-lan"`
|
AllowLan bool `json:"allow-lan"`
|
||||||
Mode T.Mode `json:"mode"`
|
Mode T.Mode `json:"mode"`
|
||||||
LogLevel log.LogLevel `json:"log-level"`
|
LogLevel log.LogLevel `json:"log-level"`
|
||||||
ExternalController string `json:"external-controller,omitempty"`
|
ExternalController string
|
||||||
Secret string `json:"secret,omitempty"`
|
ExternalUI string
|
||||||
|
Secret string
|
||||||
}
|
}
|
||||||
|
|
||||||
// DNS config
|
// DNS config
|
||||||
@ -66,9 +68,10 @@ type rawConfig struct {
|
|||||||
Mode T.Mode `yaml:"mode"`
|
Mode T.Mode `yaml:"mode"`
|
||||||
LogLevel log.LogLevel `yaml:"log-level"`
|
LogLevel log.LogLevel `yaml:"log-level"`
|
||||||
ExternalController string `yaml:"external-controller"`
|
ExternalController string `yaml:"external-controller"`
|
||||||
|
ExternalUI string `yaml:"external-ui"`
|
||||||
Secret string `yaml:"secret"`
|
Secret string `yaml:"secret"`
|
||||||
|
|
||||||
DNS *rawDNS `yaml:"dns"`
|
DNS rawDNS `yaml:"dns"`
|
||||||
Proxy []map[string]interface{} `yaml:"Proxy"`
|
Proxy []map[string]interface{} `yaml:"Proxy"`
|
||||||
ProxyGroup []map[string]interface{} `yaml:"Proxy Group"`
|
ProxyGroup []map[string]interface{} `yaml:"Proxy Group"`
|
||||||
Rule []string `yaml:"Rule"`
|
Rule []string `yaml:"Rule"`
|
||||||
@ -95,7 +98,7 @@ func readConfig(path string) (*rawConfig, error) {
|
|||||||
Rule: []string{},
|
Rule: []string{},
|
||||||
Proxy: []map[string]interface{}{},
|
Proxy: []map[string]interface{}{},
|
||||||
ProxyGroup: []map[string]interface{}{},
|
ProxyGroup: []map[string]interface{}{},
|
||||||
DNS: &rawDNS{
|
DNS: rawDNS{
|
||||||
Enable: false,
|
Enable: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -145,10 +148,19 @@ func parseGeneral(cfg *rawConfig) (*General, error) {
|
|||||||
redirPort := cfg.RedirPort
|
redirPort := cfg.RedirPort
|
||||||
allowLan := cfg.AllowLan
|
allowLan := cfg.AllowLan
|
||||||
externalController := cfg.ExternalController
|
externalController := cfg.ExternalController
|
||||||
|
externalUI := cfg.ExternalUI
|
||||||
secret := cfg.Secret
|
secret := cfg.Secret
|
||||||
mode := cfg.Mode
|
mode := cfg.Mode
|
||||||
logLevel := cfg.LogLevel
|
logLevel := cfg.LogLevel
|
||||||
|
|
||||||
|
if !filepath.IsAbs(externalUI) {
|
||||||
|
externalUI = filepath.Join(C.Path.HomeDir(), externalUI)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := os.Stat(externalUI); os.IsNotExist(err) {
|
||||||
|
return nil, fmt.Errorf("external-ui: %s not exist", externalUI)
|
||||||
|
}
|
||||||
|
|
||||||
general := &General{
|
general := &General{
|
||||||
Port: port,
|
Port: port,
|
||||||
SocksPort: socksPort,
|
SocksPort: socksPort,
|
||||||
@ -157,6 +169,7 @@ func parseGeneral(cfg *rawConfig) (*General, error) {
|
|||||||
Mode: mode,
|
Mode: mode,
|
||||||
LogLevel: logLevel,
|
LogLevel: logLevel,
|
||||||
ExternalController: externalController,
|
ExternalController: externalController,
|
||||||
|
ExternalUI: externalUI,
|
||||||
Secret: secret,
|
Secret: secret,
|
||||||
}
|
}
|
||||||
return general, nil
|
return general, nil
|
||||||
@ -353,7 +366,6 @@ func hostWithDefaultPort(host string, defPort string) (string, error) {
|
|||||||
|
|
||||||
func parseNameServer(servers []string) ([]dns.NameServer, error) {
|
func parseNameServer(servers []string) ([]dns.NameServer, error) {
|
||||||
nameservers := []dns.NameServer{}
|
nameservers := []dns.NameServer{}
|
||||||
log.Debugln("%#v", servers)
|
|
||||||
|
|
||||||
for idx, server := range servers {
|
for idx, server := range servers {
|
||||||
// parse without scheme .e.g 8.8.8.8:53
|
// parse without scheme .e.g 8.8.8.8:53
|
||||||
@ -387,7 +399,7 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) {
|
|||||||
return nameservers, nil
|
return nameservers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseDNS(cfg *rawDNS) (*DNS, error) {
|
func parseDNS(cfg rawDNS) (*DNS, error) {
|
||||||
if cfg.Enable && len(cfg.NameServer) == 0 {
|
if cfg.Enable && len(cfg.NameServer) == 0 {
|
||||||
return nil, fmt.Errorf("If DNS configuration is turned on, NameServer cannot be empty")
|
return nil, fmt.Errorf("If DNS configuration is turned on, NameServer cannot be empty")
|
||||||
}
|
}
|
||||||
|
@ -244,8 +244,6 @@ func New(config Config) *Resolver {
|
|||||||
mmdb, _ = geoip2.Open(C.Path.MMDB())
|
mmdb, _ = geoip2.Open(C.Path.MMDB())
|
||||||
})
|
})
|
||||||
|
|
||||||
println(config.EnhancedMode)
|
|
||||||
|
|
||||||
r := &Resolver{
|
r := &Resolver{
|
||||||
main: transform(config.Main),
|
main: transform(config.Main),
|
||||||
ipv6: config.IPv6,
|
ipv6: config.IPv6,
|
||||||
|
@ -27,7 +27,6 @@ func ApplyConfig(cfg *config.Config, force bool) {
|
|||||||
}
|
}
|
||||||
updateProxies(cfg.Proxies)
|
updateProxies(cfg.Proxies)
|
||||||
updateRules(cfg.Rules)
|
updateRules(cfg.Rules)
|
||||||
updateGeneral(cfg.General)
|
|
||||||
updateDNS(cfg.DNS)
|
updateDNS(cfg.DNS)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,10 @@ func Parse() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cfg.General.ExternalUI != "" {
|
||||||
|
route.SetUIPath(cfg.General.ExternalUI)
|
||||||
|
}
|
||||||
|
|
||||||
if cfg.General.ExternalController != "" {
|
if cfg.General.ExternalController != "" {
|
||||||
go route.Start(cfg.General.ExternalController, cfg.General.Secret)
|
go route.Start(cfg.General.ExternalController, cfg.General.Secret)
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ type configSchema struct {
|
|||||||
|
|
||||||
func getConfigs(w http.ResponseWriter, r *http.Request) {
|
func getConfigs(w http.ResponseWriter, r *http.Request) {
|
||||||
general := executor.GetGeneral()
|
general := executor.GetGeneral()
|
||||||
render.Respond(w, r, general)
|
render.JSON(w, r, general)
|
||||||
}
|
}
|
||||||
|
|
||||||
func pointerOrDefault(p *int, def int) int {
|
func pointerOrDefault(p *int, def int) int {
|
||||||
@ -46,8 +46,8 @@ func pointerOrDefault(p *int, def int) int {
|
|||||||
func patchConfigs(w http.ResponseWriter, r *http.Request) {
|
func patchConfigs(w http.ResponseWriter, r *http.Request) {
|
||||||
general := &configSchema{}
|
general := &configSchema{}
|
||||||
if err := render.DecodeJSON(r.Body, general); err != nil {
|
if err := render.DecodeJSON(r.Body, general); err != nil {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
render.Status(r, http.StatusBadRequest)
|
||||||
render.Respond(w, r, ErrBadRequest)
|
render.JSON(w, r, ErrBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,7 +68,7 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) {
|
|||||||
log.SetLevel(*general.LogLevel)
|
log.SetLevel(*general.LogLevel)
|
||||||
}
|
}
|
||||||
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
render.NoContent(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
type updateConfigRequest struct {
|
type updateConfigRequest struct {
|
||||||
@ -78,25 +78,25 @@ type updateConfigRequest struct {
|
|||||||
func updateConfigs(w http.ResponseWriter, r *http.Request) {
|
func updateConfigs(w http.ResponseWriter, r *http.Request) {
|
||||||
req := updateConfigRequest{}
|
req := updateConfigRequest{}
|
||||||
if err := render.DecodeJSON(r.Body, &req); err != nil {
|
if err := render.DecodeJSON(r.Body, &req); err != nil {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
render.Status(r, http.StatusBadRequest)
|
||||||
render.Respond(w, r, ErrBadRequest)
|
render.JSON(w, r, ErrBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !filepath.IsAbs(req.Path) {
|
if !filepath.IsAbs(req.Path) {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
render.Status(r, http.StatusBadRequest)
|
||||||
render.Respond(w, r, newError("path is not a absoluted path"))
|
render.JSON(w, r, newError("path is not a absoluted path"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
force := r.URL.Query().Get("force") == "true"
|
force := r.URL.Query().Get("force") == "true"
|
||||||
cfg, err := executor.ParseWithPath(req.Path)
|
cfg, err := executor.ParseWithPath(req.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
render.Status(r, http.StatusBadRequest)
|
||||||
render.Respond(w, r, newError(err.Error()))
|
render.JSON(w, r, newError(err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
executor.ApplyConfig(cfg, force)
|
executor.ApplyConfig(cfg, force)
|
||||||
w.WriteHeader(http.StatusNoContent)
|
render.NoContent(w, r)
|
||||||
}
|
}
|
||||||
|
@ -47,8 +47,8 @@ func findProxyByName(next http.Handler) http.Handler {
|
|||||||
proxies := T.Instance().Proxies()
|
proxies := T.Instance().Proxies()
|
||||||
proxy, exist := proxies[name]
|
proxy, exist := proxies[name]
|
||||||
if !exist {
|
if !exist {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
render.Status(r, http.StatusNotFound)
|
||||||
render.Respond(w, r, ErrNotFound)
|
render.JSON(w, r, ErrNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,14 +59,14 @@ func findProxyByName(next http.Handler) http.Handler {
|
|||||||
|
|
||||||
func getProxies(w http.ResponseWriter, r *http.Request) {
|
func getProxies(w http.ResponseWriter, r *http.Request) {
|
||||||
proxies := T.Instance().Proxies()
|
proxies := T.Instance().Proxies()
|
||||||
render.Respond(w, r, map[string]map[string]C.Proxy{
|
render.JSON(w, r, map[string]map[string]C.Proxy{
|
||||||
"proxies": proxies,
|
"proxies": proxies,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func getProxy(w http.ResponseWriter, r *http.Request) {
|
func getProxy(w http.ResponseWriter, r *http.Request) {
|
||||||
proxy := r.Context().Value(CtxKeyProxy).(C.Proxy)
|
proxy := r.Context().Value(CtxKeyProxy).(C.Proxy)
|
||||||
render.Respond(w, r, proxy)
|
render.JSON(w, r, proxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateProxyRequest struct {
|
type UpdateProxyRequest struct {
|
||||||
@ -76,8 +76,8 @@ type UpdateProxyRequest struct {
|
|||||||
func updateProxy(w http.ResponseWriter, r *http.Request) {
|
func updateProxy(w http.ResponseWriter, r *http.Request) {
|
||||||
req := UpdateProxyRequest{}
|
req := UpdateProxyRequest{}
|
||||||
if err := render.DecodeJSON(r.Body, &req); err != nil {
|
if err := render.DecodeJSON(r.Body, &req); err != nil {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
render.Status(r, http.StatusBadRequest)
|
||||||
render.Respond(w, r, ErrBadRequest)
|
render.JSON(w, r, ErrBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,18 +85,18 @@ func updateProxy(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
selector, ok := proxy.(*A.Selector)
|
selector, ok := proxy.(*A.Selector)
|
||||||
if !ok {
|
if !ok {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
render.Status(r, http.StatusBadRequest)
|
||||||
render.Respond(w, r, ErrBadRequest)
|
render.JSON(w, r, ErrBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := selector.Set(req.Name); err != nil {
|
if err := selector.Set(req.Name); err != nil {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
render.Status(r, http.StatusBadRequest)
|
||||||
render.Respond(w, r, newError(fmt.Sprintf("Selector update error: %s", err.Error())))
|
render.JSON(w, r, newError(fmt.Sprintf("Selector update error: %s", err.Error())))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
render.NoContent(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getProxyDelay(w http.ResponseWriter, r *http.Request) {
|
func getProxyDelay(w http.ResponseWriter, r *http.Request) {
|
||||||
@ -104,8 +104,8 @@ func getProxyDelay(w http.ResponseWriter, r *http.Request) {
|
|||||||
url := query.Get("url")
|
url := query.Get("url")
|
||||||
timeout, err := strconv.ParseInt(query.Get("timeout"), 10, 16)
|
timeout, err := strconv.ParseInt(query.Get("timeout"), 10, 16)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
render.Status(r, http.StatusBadRequest)
|
||||||
render.Respond(w, r, ErrBadRequest)
|
render.JSON(w, r, ErrBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,14 +122,14 @@ func getProxyDelay(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
select {
|
select {
|
||||||
case <-time.After(time.Millisecond * time.Duration(timeout)):
|
case <-time.After(time.Millisecond * time.Duration(timeout)):
|
||||||
w.WriteHeader(http.StatusRequestTimeout)
|
render.Status(r, http.StatusRequestTimeout)
|
||||||
render.Respond(w, r, ErrRequestTimeout)
|
render.JSON(w, r, ErrRequestTimeout)
|
||||||
case t := <-sigCh:
|
case t := <-sigCh:
|
||||||
if t == 0 {
|
if t == 0 {
|
||||||
w.WriteHeader(http.StatusServiceUnavailable)
|
render.Status(r, http.StatusServiceUnavailable)
|
||||||
render.Respond(w, r, newError("An error occurred in the delay test"))
|
render.JSON(w, r, newError("An error occurred in the delay test"))
|
||||||
} else {
|
} else {
|
||||||
render.Respond(w, r, map[string]int16{
|
render.JSON(w, r, map[string]int16{
|
||||||
"delay": t,
|
"delay": t,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -33,8 +33,7 @@ func getRules(w http.ResponseWriter, r *http.Request) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
render.JSON(w, r, map[string][]Rule{
|
||||||
render.Respond(w, r, map[string][]Rule{
|
|
||||||
"rules": rules,
|
"rules": rules,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,8 @@ import (
|
|||||||
var (
|
var (
|
||||||
serverSecret = ""
|
serverSecret = ""
|
||||||
serverAddr = ""
|
serverAddr = ""
|
||||||
|
|
||||||
|
uiPath = ""
|
||||||
)
|
)
|
||||||
|
|
||||||
type Traffic struct {
|
type Traffic struct {
|
||||||
@ -24,6 +26,10 @@ type Traffic struct {
|
|||||||
Down int64 `json:"down"`
|
Down int64 `json:"down"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SetUIPath(path string) {
|
||||||
|
uiPath = path
|
||||||
|
}
|
||||||
|
|
||||||
func Start(addr string, secret string) {
|
func Start(addr string, secret string) {
|
||||||
if serverAddr != "" {
|
if serverAddr != "" {
|
||||||
return
|
return
|
||||||
@ -49,6 +55,14 @@ func Start(addr string, secret string) {
|
|||||||
r.Mount("/proxies", proxyRouter())
|
r.Mount("/proxies", proxyRouter())
|
||||||
r.Mount("/rules", ruleRouter())
|
r.Mount("/rules", ruleRouter())
|
||||||
|
|
||||||
|
if uiPath != "" {
|
||||||
|
fs := http.StripPrefix("/ui", http.FileServer(http.Dir(uiPath)))
|
||||||
|
r.Get("/ui", http.RedirectHandler("/ui/", 301).ServeHTTP)
|
||||||
|
r.Get("/ui/*", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fs.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
log.Infoln("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 {
|
||||||
@ -77,8 +91,8 @@ func authentication(next http.Handler) http.Handler {
|
|||||||
hasUnvalidHeader := text[0] != "Bearer"
|
hasUnvalidHeader := text[0] != "Bearer"
|
||||||
hasUnvalidSecret := len(text) == 2 && text[1] != serverSecret
|
hasUnvalidSecret := len(text) == 2 && text[1] != serverSecret
|
||||||
if hasUnvalidHeader || hasUnvalidSecret {
|
if hasUnvalidHeader || hasUnvalidSecret {
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
render.Status(r, http.StatusUnauthorized)
|
||||||
render.Respond(w, r, ErrUnauthorized)
|
render.JSON(w, r, ErrUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
@ -87,7 +101,7 @@ func authentication(next http.Handler) http.Handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func traffic(w http.ResponseWriter, r *http.Request) {
|
func traffic(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusOK)
|
render.Status(r, http.StatusOK)
|
||||||
|
|
||||||
tick := time.NewTicker(time.Second)
|
tick := time.NewTicker(time.Second)
|
||||||
t := T.Instance().Traffic()
|
t := T.Instance().Traffic()
|
||||||
@ -116,8 +130,8 @@ func getLogs(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
level, ok := log.LogLevelMapping[levelText]
|
level, ok := log.LogLevelMapping[levelText]
|
||||||
if !ok {
|
if !ok {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
render.Status(r, http.StatusBadRequest)
|
||||||
render.Respond(w, r, ErrBadRequest)
|
render.JSON(w, r, ErrBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
52
proxy/redir/tcp_freebsd.go
Normal file
52
proxy/redir/tcp_freebsd.go
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package redir
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/go-shadowsocks2/socks"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv4.h
|
||||||
|
IP6T_SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv6/ip6_tables.h
|
||||||
|
)
|
||||||
|
|
||||||
|
func parserPacket(conn net.Conn) (socks.Addr, error) {
|
||||||
|
c, ok := conn.(*net.TCPConn)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("only work with TCP connection")
|
||||||
|
}
|
||||||
|
|
||||||
|
rc, err := c.SyscallConn()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var addr socks.Addr
|
||||||
|
|
||||||
|
rc.Control(func(fd uintptr) {
|
||||||
|
addr, err = getorigdst(fd)
|
||||||
|
})
|
||||||
|
|
||||||
|
return addr, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call getorigdst() from linux/net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c
|
||||||
|
func getorigdst(fd uintptr) (socks.Addr, error) {
|
||||||
|
raw := syscall.RawSockaddrInet4{}
|
||||||
|
siz := unsafe.Sizeof(raw)
|
||||||
|
_, _, err := syscall.Syscall6(syscall.SYS_GETSOCKOPT, fd, syscall.IPPROTO_IP, SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&raw)), uintptr(unsafe.Pointer(&siz)), 0);
|
||||||
|
if err != 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := make([]byte, 1+net.IPv4len+2)
|
||||||
|
addr[0] = socks.AtypIPv4
|
||||||
|
copy(addr[1:1+net.IPv4len], raw.Addr[:])
|
||||||
|
port := (*[2]byte)(unsafe.Pointer(&raw.Port)) // big-endian
|
||||||
|
addr[1+net.IPv4len], addr[1+net.IPv4len+1] = port[0], port[1]
|
||||||
|
return addr, nil
|
||||||
|
}
|
@ -5,6 +5,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -25,8 +26,13 @@ func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, proxy C.ProxyAdapter)
|
|||||||
conn := newTrafficTrack(proxy.Conn(), t.traffic)
|
conn := newTrafficTrack(proxy.Conn(), t.traffic)
|
||||||
req := request.R
|
req := request.R
|
||||||
host := req.Host
|
host := req.Host
|
||||||
|
keepalive := true
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
if strings.ToLower(req.Header.Get("Connection")) == "close" {
|
||||||
|
keepalive = false
|
||||||
|
}
|
||||||
|
|
||||||
req.Header.Set("Connection", "close")
|
req.Header.Set("Connection", "close")
|
||||||
req.RequestURI = ""
|
req.RequestURI = ""
|
||||||
adapters.RemoveHopByHopHeaders(req.Header)
|
adapters.RemoveHopByHopHeaders(req.Header)
|
||||||
@ -53,6 +59,10 @@ func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, proxy C.ProxyAdapter)
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !keepalive {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
req, err = http.ReadRequest(bufio.NewReader(request.Conn()))
|
req, err = http.ReadRequest(bufio.NewReader(request.Conn()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
|
@ -120,8 +120,6 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) {
|
|||||||
log.Debugln("[DNS] %s --> %s", metadata.Host, ip.String())
|
log.Debugln("[DNS] %s --> %s", metadata.Host, ip.String())
|
||||||
metadata.IP = &ip
|
metadata.IP = &ip
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
log.Debugln("[DNS] unknown%#v", metadata)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var proxy C.Proxy
|
var proxy C.Proxy
|
||||||
|
Reference in New Issue
Block a user